From 8733e808e3b831ddba2dd0f486bf0c7a67ecac23 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Mon, 2 Oct 2023 08:49:51 -0700 Subject: [PATCH 1/8] update form validaitons Signed-off-by: Shenoy Pratik --- common/constants/index.ts | 9 + common/types/index.ts | 21 +- public/components/Main/main.tsx | 151 +++++++------ public/components/SQLPage/SQLPage.tsx | 204 ++++++++++-------- .../create/create_acceleration.tsx | 100 +++++---- .../components/acceleration/create/utils.tsx | 138 +++++++++++- .../selectors/define_index_options.tsx | 18 +- .../selectors/index_setting_options.tsx | 81 +++++-- .../selectors/index_type_selector.tsx | 20 +- .../selectors/source_selector.tsx | 50 +++-- .../materialized_view/add_column_popover.tsx | 3 +- .../skipping_index/add_fields_modal.tsx | 10 +- .../skipping_index/delete_fields_modal.tsx | 2 +- .../skipping_index/skipping_index_builder.tsx | 30 +-- 14 files changed, 557 insertions(+), 280 deletions(-) diff --git a/common/constants/index.ts b/common/constants/index.ts index e43eed0e..8533039d 100644 --- a/common/constants/index.ts +++ b/common/constants/index.ts @@ -32,7 +32,16 @@ export const ACCELERATION_TIME_INTERVAL = [ { text: 'week(s)', value: 'week' }, ]; +export const SKIPPING_INDEX_ACCELERATION_METHODS = [ + { value: 'PARTITION', text: 'Partition' }, + { value: 'VALUE_SET', text: 'Value Set' }, + { value: 'MIN_MAX', text: 'Min Max' }, +]; + export const ACCELERATION_ADD_FIELDS_TEXT = '(add fields here)'; +export const ACCELERATION_INDEX_NAME_REGEX = /^[a-z][a-z_\-]*$/; +export const ACCELERATION_S3_URL_REGEX = /^(s3|s3a):\/\/[a-zA-Z0-9.\-]+\/.*/; +export const ACCELERATION_DEFUALT_SKIPPING_INDEX_NAME = 'skipping'; 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 diff --git a/common/types/index.ts b/common/types/index.ts index ed1783a9..928d7578 100644 --- a/common/types/index.ts +++ b/common/types/index.ts @@ -12,11 +12,13 @@ export interface MaterializedViewColumn { fieldAlias?: string; } +export type SkippingIndexAccMethodType = 'PARTITION' | 'VALUE_SET' | 'MIN_MAX'; + export interface SkippingIndexRowType { id: string; fieldName: string; dataType: string; - accelerationMethod: 'PARTITION' | 'VALUE_SET' | 'MIN_MAX'; + accelerationMethod: SkippingIndexAccMethodType; } export interface DataTableFieldsType { @@ -43,6 +45,20 @@ export interface materializedViewQueryType { groupByTumbleValue: GroupByTumbleType; } +export interface FormErrorsType { + dataSourceError: string[]; + databaseError: string[]; + dataTableError: string[]; + skippingIndexError: string[]; + coveringIndexError: string[]; + materializedViewError: string[]; + indexNameError: string[]; + primaryShardsError: string[]; + replicaShardsError: string[]; + refreshIntervalError: string[]; + checkpointLocationError: string[]; +} + export interface CreateAccelerationForm { dataSource: string; database: string; @@ -57,5 +73,6 @@ export interface CreateAccelerationForm { replicaShardsCount: number; refreshType: 'interval' | 'auto'; checkpointLocation: string | undefined; - refreshIntervalOptions: RefreshIntervalType | undefined; + refreshIntervalOptions: RefreshIntervalType; + formErrors: FormErrorsType; } diff --git a/public/components/Main/main.tsx b/public/components/Main/main.tsx index a80bdd41..e9f30cbc 100644 --- a/public/components/Main/main.tsx +++ b/public/components/Main/main.tsx @@ -3,18 +3,18 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { - EuiButton, - EuiFlexGroup, +import { + EuiButton, + EuiComboBox, + EuiFlexGroup, EuiFlexItem, - EuiSpacer, - EuiPageSideBar , - EuiPanel, - EuiPage, + EuiPage, EuiPageContent, - EuiPageContentBody, - EuiComboBox, - EuiText, + EuiPageContentBody, + EuiPageSideBar, + EuiPanel, + EuiSpacer, + EuiText, } from '@elastic/eui'; import { IHttpResponse } from 'angular'; import _ from 'lodash'; @@ -22,17 +22,17 @@ import React from 'react'; import { ChromeBreadcrumb, CoreStart } from '../../../../../src/core/public'; import { MESSAGE_TAB_LABEL } from '../../utils/constants'; import { + Tree, getDefaultTabId, getDefaultTabLabel, getQueries, getSelectedResults, - Tree, } from '../../utils/utils'; import { PPLPage } from '../PPLPage/PPLPage'; import Switch from '../QueryLanguageSwitch/Switch'; import QueryResults from '../QueryResults/QueryResults'; import { SQLPage } from '../SQLPage/SQLPage'; -import { TableView } from '../SQLPage/TableView' +import { TableView } from '../SQLPage/TableView'; interface ResponseData { ok: boolean; @@ -217,7 +217,7 @@ export class Main extends React.Component { this.onChange = this.onChange.bind(this); this.state = { language: 'SQL', - sqlQueriesString: 'SHOW tables LIKE \'%\';', + sqlQueriesString: "SHOW tables LIKE '%';", pplQueriesString: '', queries: [], queryTranslations: [], @@ -232,7 +232,7 @@ export class Main extends React.Component { itemIdToExpandedRowMap: {}, messages: [], isResultFullScreen: false, - selectedDatasource: '' + selectedDatasource: '', }; this.httpClient = this.props.httpClient; this.updateSQLQueries = _.debounce(this.updateSQLQueries, 250).bind(this); @@ -607,11 +607,11 @@ export class Main extends React.Component { ); // added callback function to handle async issues }; - updateSQLQueries(query: string) { + updateSQLQueries = (query: string) => { this.setState({ sqlQueriesString: query, }); - } + }; updatePPLQueries(query: string) { this.setState({ @@ -626,11 +626,11 @@ export class Main extends React.Component { } handleComboOptionChange = (selectedOption: string) => { - this.setState({ - selectedDatasource: selectedOption }); + this.setState({ + selectedDatasource: selectedOption, + }); }; - render() { let page; let link; @@ -645,6 +645,7 @@ export class Main extends React.Component { sqlQuery={this.state.sqlQueriesString} sqlTranslations={this.state.queryTranslations} updateSQLQueries={this.updateSQLQueries} + selectedDatasource={this.state.selectedDatasource} /> ); link = 'https://opensearch.org/docs/latest/search-plugins/sql/index/'; @@ -706,17 +707,19 @@ export class Main extends React.Component { return ( <> - + Data Sources { const selectedValue = selectedOptions[0] ? selectedOptions[0].value : ''; this.handleComboOptionChange(selectedValue); @@ -733,26 +736,22 @@ export class Main extends React.Component { - + {this.state.language === 'SQL' && ( - + Create - + /> @@ -760,56 +759,54 @@ export class Main extends React.Component { )} - + - - +
{page}
- -
- -
-
-
-
+ +
+ +
+ + +
- ); } } diff --git a/public/components/SQLPage/SQLPage.tsx b/public/components/SQLPage/SQLPage.tsx index 94808e17..0cc0bac4 100644 --- a/public/components/SQLPage/SQLPage.tsx +++ b/public/components/SQLPage/SQLPage.tsx @@ -3,43 +3,43 @@ * SPDX-License-Identifier: Apache-2.0 */ - -import React from "react"; import { - EuiPanel, EuiButton, + EuiCodeBlock, + EuiCodeEditor, EuiFlexGroup, EuiFlexItem, - EuiOverlayMask, EuiModal, - EuiModalHeader, - EuiModalHeaderTitle, EuiModalBody, EuiModalFooter, - EuiCodeBlock, - EuiText, - EuiCodeEditor, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask, + EuiPanel, EuiSpacer, -} from "@elastic/eui"; -import { ResponseDetail, TranslateResult } from '../Main/main'; -import _ from 'lodash'; -import "brace/mode/sql"; -import "../../ace-themes/sql_console"; +} from '@elastic/eui'; import 'brace/ext/language_tools'; +import 'brace/mode/sql'; +import React from 'react'; +import '../../ace-themes/sql_console'; +import { ResponseDetail, TranslateResult } from '../Main/main'; +import { CreateAcceleration } from '../acceleration/create/create_acceleration'; interface SQLPageProps { - onRun: (query: string) => void, - onTranslate: (query: string) => void, - onClear: () => void, - updateSQLQueries: (query: string) => void - sqlQuery: string, - sqlTranslations: ResponseDetail[] + onRun: (query: string) => void; + onTranslate: (query: string) => void; + onClear: () => void; + updateSQLQueries: (query: string) => void; + sqlQuery: string; + sqlTranslations: ResponseDetail[]; + selectedDatasource: string; } interface SQLPageState { - sqlQuery: string, - translation: string, - isModalVisible: boolean + sqlQuery: string; + translation: string; + isModalVisible: boolean; + flyoutComponent: JSX.Element; } export class SQLPage extends React.Component { @@ -47,19 +47,35 @@ export class SQLPage extends React.Component { super(props); this.state = { sqlQuery: this.props.sqlQuery, - translation: "", - isModalVisible: false + translation: '', + isModalVisible: false, + flyoutComponent: <>, }; } setIsModalVisible(visible: boolean): void { this.setState({ - isModalVisible: visible - }) + isModalVisible: visible, + }); } - render() { + resetFlyout = () => { + this.setState({ flyoutComponent: <> }); + }; + setAccelerationFlyout = () => { + this.setState({ + flyoutComponent: ( + + ), + }); + }; + + render() { const closeModal = () => this.setIsModalVisible(false); const showModal = () => this.setIsModalVisible(true); @@ -68,11 +84,13 @@ export class SQLPage extends React.Component { return this.props.sqlTranslations[0].fulfilled; } return false; - } + }; const explainContent = sqlTranslationsNotEmpty() - ? this.props.sqlTranslations.map((queryTranslation: any) => JSON.stringify(queryTranslation.data, null, 2)).join("\n") - : 'This query is not explainable.'; + ? this.props.sqlTranslations + .map((queryTranslation: any) => JSON.stringify(queryTranslation.data, null, 2)) + .join('\n') + : 'This query is not explainable.'; let modal; @@ -85,11 +103,7 @@ export class SQLPage extends React.Component { - + {explainContent} @@ -97,7 +111,7 @@ export class SQLPage extends React.Component { Close - + @@ -105,60 +119,68 @@ export class SQLPage extends React.Component { } return ( - - - - - - this.props.onRun(this.props.sqlQuery)} - > - - Run - - - { - this.props.updateSQLQueries(""); - this.props.onClear(); + <> + + + - - Clear - - - - this.props.onTranslate(this.props.sqlQuery) - } - > - - Explain - - {modal} - - - - ) + aria-label="Code Editor" + /> + + + + + this.props.onRun(this.props.sqlQuery)}> + + Run + + + { + this.props.updateSQLQueries(''); + this.props.onClear(); + }} + > + Clear + + this.props.onTranslate(this.props.sqlQuery)} + > + + Explain + + + + + {this.props.selectedDatasource === 'S3' && ( + + + Accelerate Table + + + )} + + + {modal} + {this.state.flyoutComponent} + + ); } } diff --git a/public/components/acceleration/create/create_acceleration.tsx b/public/components/acceleration/create/create_acceleration.tsx index e726adf0..f667661c 100644 --- a/public/components/acceleration/create/create_acceleration.tsx +++ b/public/components/acceleration/create/create_acceleration.tsx @@ -12,11 +12,15 @@ import { EuiFlyoutBody, EuiFlyoutFooter, EuiFlyoutHeader, + EuiForm, EuiSpacer, } from '@elastic/eui'; import React, { useState } from 'react'; -import { ACCELERATION_TIME_INTERVAL } from '../../../../common/constants'; -import { CreateAccelerationForm } from '../../../../common/types/'; +import { + ACCELERATION_DEFUALT_SKIPPING_INDEX_NAME, + 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'; @@ -24,16 +28,17 @@ import { accelerationQueryBuilder } from '../visual_editors/query_builder'; import { QueryVisualEditor } from '../visual_editors/query_visual_editor'; import { CautionBannerCallout } from './caution_banner_callout'; import { CreateAccelerationHeader } from './create_acceleration_header'; +import { formValidator, hasError } from './utils'; export interface CreateAccelerationProps { dataSource: string; - setIsFlyoutVisible(visible: boolean): void; + resetFlyout: () => void; updateQueries: (query: string) => void; } export const CreateAcceleration = ({ dataSource, - setIsFlyoutVisible, + resetFlyout, updateQueries, }: CreateAccelerationProps) => { const [accelerationFormData, setAccelerationFormData] = useState({ @@ -52,7 +57,7 @@ export const CreateAcceleration = ({ tumbleInterval: '', }, }, - accelerationIndexName: '', + accelerationIndexName: ACCELERATION_DEFUALT_SKIPPING_INDEX_NAME, primaryShardsCount: 5, replicaShardsCount: 1, refreshType: 'auto', @@ -61,65 +66,76 @@ export const CreateAcceleration = ({ refreshWindow: 1, refreshInterval: ACCELERATION_TIME_INTERVAL[1].value, }, + formErrors: { + dataSourceError: [], + databaseError: [], + dataTableError: [], + skippingIndexError: [], + coveringIndexError: [], + materializedViewError: [], + indexNameError: [], + primaryShardsError: [], + replicaShardsError: [], + refreshIntervalError: [], + checkpointLocationError: [], + }, }); const copyToEditor = () => { + const errors = formValidator(accelerationFormData); + if (Object.values(errors).some((e) => !!e.length)) { + setAccelerationFormData({ ...accelerationFormData, formErrors: errors }); + return; + } updateQueries(accelerationQueryBuilder(accelerationFormData)); + resetFlyout(); }; 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/utils.tsx b/public/components/acceleration/create/utils.tsx index 798a94ae..b83260fc 100644 --- a/public/components/acceleration/create/utils.tsx +++ b/public/components/acceleration/create/utils.tsx @@ -3,13 +3,147 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ACCELERATION_INDEX_NAME_REGEX } from '../../../../common/constants'; +import { + ACCELERATION_INDEX_NAME_REGEX, + ACCELERATION_S3_URL_REGEX, +} from '../../../../common/constants'; +import { + AccelerationIndexType, + CreateAccelerationForm, + FormErrorsType, + SkippingIndexRowType, + materializedViewQueryType, +} from '../../../../common/types'; export const pluralizeTime = (timeWindow: number) => { return timeWindow > 1 ? 's' : ''; }; +export const hasError = (formErrors: FormErrorsType, key?: keyof FormErrorsType) => { + if (!key) return Object.values(formErrors).some((e) => !!e.length); + return !!formErrors[key]?.length; +}; + +export const validateDataSource = (dataSource: string) => { + return dataSource.trim().length === 0 ? ['Select a valid data source'] : []; +}; + +export const validateDatabase = (database: string) => { + return database.trim().length === 0 ? ['Select a valid database'] : []; +}; + +export const validateDataTable = (dataTable: string) => { + return dataTable.trim().length === 0 ? ['Select a valid table'] : []; +}; + +export const validatePrimaryShardCount = (primaryShardCount: number) => { + return primaryShardCount < 1 ? ['Primary shards count should be greater than 0'] : []; +}; + +export const validateReplicaCount = (replicaCount: number) => { + return replicaCount < 1 ? ['Replica count should be greater than 0'] : []; +}; + +export const validateRefreshInterval = (refreshType: string, refreshWindow: number) => { + return refreshType === 'interval' && refreshWindow < 1 + ? ['refresh window should be greater than 0'] + : []; +}; + export const validateIndexName = (value: string) => { // Check if the value does not begin with underscores or hyphens and all characters are lower case - return ACCELERATION_INDEX_NAME_REGEX.test(value); + return !ACCELERATION_INDEX_NAME_REGEX.test(value) ? ['Enter a valid index name'] : []; +}; + +export const validateCheckpointLocation = ( + accelerationIndexType: AccelerationIndexType, + checkpointLocation: string | undefined +) => { + if (accelerationIndexType === 'materialized' && !checkpointLocation) { + return ['Checkpoint location is mandatory for materialized view creation']; + } + + if (checkpointLocation && !ACCELERATION_S3_URL_REGEX.test(checkpointLocation)) + return ['Enter a valid checkpoint location']; + + return []; +}; + +export const validateSkippingIndexData = ( + accelerationIndexType: AccelerationIndexType, + skippingIndexQueryData: SkippingIndexRowType[] +) => { + // TODO: Validate dataType match with supported acceleration method type + if (accelerationIndexType !== 'skipping') return []; + + if (skippingIndexQueryData.length < 1) return ['Add fields to the skipping index definition']; + + return []; +}; + +export const validateCoveringIndexData = ( + accelerationIndexType: AccelerationIndexType, + coveringIndexQueryData: string[] +) => { + if (accelerationIndexType !== 'covering') return []; + + if (coveringIndexQueryData.length < 1) return ['Add fields to covering index definition']; + return []; +}; + +export const validateMaterializedViewData = ( + accelerationIndexType: AccelerationIndexType, + materializedViewQueryData: materializedViewQueryType +) => { + if (accelerationIndexType !== 'materialized') return []; + + if (materializedViewQueryData.columnsValues.length < 1) + return ['Add columns to materialized view definition']; + + if (materializedViewQueryData.groupByTumbleValue.timeField !== '') + return ['Add a time field to tumble function in materialized view definition']; + + if (materializedViewQueryData.groupByTumbleValue.tumbleWindow > 0) + return ['Add a valid time window to tumble function in materialized view definition']; + return []; +}; + +export const formValidator = (accelerationformData: CreateAccelerationForm) => { + const { + dataSource, + database, + dataTable, + accelerationIndexType, + skippingIndexQueryData, + coveringIndexQueryData, + materializedViewQueryData, + accelerationIndexName, + primaryShardsCount, + replicaShardsCount, + refreshType, + checkpointLocation, + refreshIntervalOptions, + } = accelerationformData; + + const accelerationFormErrors = { + dataSourceError: validateDataSource(dataSource), + databaseError: validateDatabase(database), + dataTableError: validateDataTable(dataTable), + primaryShardsError: validatePrimaryShardCount(primaryShardsCount), + replicaShardsError: validateReplicaCount(replicaShardsCount), + refreshIntervalError: validateRefreshInterval( + refreshType, + refreshIntervalOptions.refreshWindow + ), + checkpointLocationError: validateCheckpointLocation(accelerationIndexType, checkpointLocation), + indexNameError: validateIndexName(accelerationIndexName), + skippingIndexError: validateSkippingIndexData(accelerationIndexType, skippingIndexQueryData), + coveringIndexError: validateCoveringIndexData(accelerationIndexType, coveringIndexQueryData), + materializedViewError: validateMaterializedViewData( + accelerationIndexType, + materializedViewQueryData + ), + }; + + return accelerationFormErrors; }; diff --git a/public/components/acceleration/selectors/define_index_options.tsx b/public/components/acceleration/selectors/define_index_options.tsx index e43f7258..72ad4654 100644 --- a/public/components/acceleration/selectors/define_index_options.tsx +++ b/public/components/acceleration/selectors/define_index_options.tsx @@ -20,10 +20,11 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; +import producer from 'immer'; 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 { hasError, validateIndexName } from '../create/utils'; interface DefineIndexOptionsProps { accelerationFormData: CreateAccelerationForm; @@ -34,7 +35,6 @@ export const DefineIndexOptions = ({ accelerationFormData, setAccelerationFormData, }: DefineIndexOptionsProps) => { - const [indexName, setIndexName] = useState(''); const [modalComponent, setModalComponent] = useState(<>); const modalValue = ( @@ -61,7 +61,6 @@ export const DefineIndexOptions = ({ const onChangeIndexName = (e: ChangeEvent) => { setAccelerationFormData({ ...accelerationFormData, accelerationIndexName: e.target.value }); - setIndexName(e.target.value); }; const getPreprend = () => { @@ -96,6 +95,8 @@ export const DefineIndexOptions = ({ label="Index name" helpText='Must be in lowercase letters. Cannot begin with underscores or hyphens. Spaces, commas, and characters :, ", *, +, /, \, |, ?, #, >, or < are not allowed. Prefix and suffix are added to the name of generated OpenSearch index.' + isInvalid={hasError(accelerationFormData.formErrors, 'indexNameError')} + error={accelerationFormData.formErrors.indexNameError} labelAppend={ setModalComponent(modalValue)}>Help @@ -104,13 +105,20 @@ export const DefineIndexOptions = ({ > { + setAccelerationFormData( + producer((accData) => { + accData.formErrors.indexNameError = validateIndexName(e.target.value); + }) + ); + }} /> {modalComponent} diff --git a/public/components/acceleration/selectors/index_setting_options.tsx b/public/components/acceleration/selectors/index_setting_options.tsx index 02ec6a2a..7aec7ca0 100644 --- a/public/components/acceleration/selectors/index_setting_options.tsx +++ b/public/components/acceleration/selectors/index_setting_options.tsx @@ -12,9 +12,17 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; +import producer from 'immer'; import React, { ChangeEvent, useState } from 'react'; import { ACCELERATION_TIME_INTERVAL } from '../../../../common/constants'; import { CreateAccelerationForm } from '../../../../common/types'; +import { + hasError, + validateCheckpointLocation, + validatePrimaryShardCount, + validateRefreshInterval, + validateReplicaCount, +} from '../create/utils'; import { IndexTypeSelector } from './index_type_selector'; interface IndexSettingOptionsProps { @@ -68,25 +76,21 @@ export const IndexSettingOptions = ({ const onChangeRefreshWindow = (e: ChangeEvent) => { const windowCount = +e.target.value; - setAccelerationFormData({ - ...accelerationFormData, - refreshIntervalOptions: { - ...accelerationFormData.refreshIntervalOptions, - refreshWindow: windowCount, - }, - }); + setAccelerationFormData( + producer((accData) => { + accData.refreshIntervalOptions.refreshWindow = windowCount; + }) + ); setRefreshWindow(windowCount); }; const onChangeRefreshInterval = (e: React.ChangeEvent) => { const refreshIntervalValue = e.target.value; - setAccelerationFormData({ - ...accelerationFormData, - refreshIntervalOptions: { - ...accelerationFormData.refreshIntervalOptions, - refreshInterval: refreshIntervalValue, - }, - }); + setAccelerationFormData( + producer((accData) => { + accData.refreshIntervalOptions.refreshInterval = refreshIntervalValue; + }) + ); setRefreshInterval(refreshIntervalValue); }; @@ -110,6 +114,8 @@ export const IndexSettingOptions = ({ label="Number of primary shards" helpText="Specify the number of primary shards for the index. Default is 5. The number of primary shards cannot be changed after the index is created." + isInvalid={hasError(accelerationFormData.formErrors, 'primaryShardsError')} + error={accelerationFormData.formErrors.primaryShardsError} > { + setAccelerationFormData( + producer((accData) => { + accData.formErrors.primaryShardsError = validatePrimaryShardCount(+e.target.value); + }) + ); + }} + isInvalid={hasError(accelerationFormData.formErrors, 'primaryShardsError')} /> { + setAccelerationFormData( + producer((accData) => { + accData.formErrors.replicaShardsError = validateReplicaCount(+e.target.value); + }) + ); + }} + isInvalid={hasError(accelerationFormData.formErrors, 'replicaShardsError')} /> { + setAccelerationFormData( + producer((accData) => { + accData.formErrors.refreshIntervalError = validateRefreshInterval( + refreshTypeSelected, + +e.target.value + ); + }) + ); + }} append={ { + setAccelerationFormData( + producer((accData) => { + accData.formErrors.checkpointLocationError = validateCheckpointLocation( + accData.accelerationIndexType, + e.target.value + ); + }) + ); + }} /> diff --git a/public/components/acceleration/selectors/index_type_selector.tsx b/public/components/acceleration/selectors/index_type_selector.tsx index fe822322..06b7828d 100644 --- a/public/components/acceleration/selectors/index_type_selector.tsx +++ b/public/components/acceleration/selectors/index_type_selector.tsx @@ -6,6 +6,7 @@ import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow, EuiLink, EuiText } from '@elastic/eui'; import React, { useState } from 'react'; import { + ACCELERATION_DEFUALT_SKIPPING_INDEX_NAME, ACCELERATION_INDEX_TYPES, ACC_INDEX_TYPE_DOCUMENTATION_URL, } from '../../../../common/constants'; @@ -23,6 +24,17 @@ export const IndexTypeSelector = ({ const [selectedIndexType, setSelectedIndexType] = useState[]>([ ACCELERATION_INDEX_TYPES[0], ]); + + const onChangeIndexType = (indexTypeOption: EuiComboBoxOptionOption[]) => { + const indexType = indexTypeOption[0].value as AccelerationIndexType; + setAccelerationFormData({ + ...accelerationFormData, + accelerationIndexType: indexType, + accelerationIndexName: + indexType === 'skipping' ? ACCELERATION_DEFUALT_SKIPPING_INDEX_NAME : '', + }); + setSelectedIndexType(indexTypeOption); + }; return ( <> { - setAccelerationFormData({ - ...accelerationFormData, - accelerationIndexType: indexType[0].value as AccelerationIndexType, - }); - setSelectedIndexType(indexType); - }} + onChange={onChangeIndexType} 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 02ffc251..a0f068ba 100644 --- a/public/components/acceleration/selectors/source_selector.tsx +++ b/public/components/acceleration/selectors/source_selector.tsx @@ -11,8 +11,10 @@ import { EuiText, htmlIdGenerator, } from '@elastic/eui'; +import producer from 'immer'; import React, { useEffect, useState } from 'react'; import { CreateAccelerationForm } from '../../../../common/types'; +import { hasError, validateDataSource } from '../create/utils'; interface AccelerationDataSourceSelectorProps { accelerationFormData: CreateAccelerationForm; @@ -109,6 +111,8 @@ export const AccelerationDataSourceSelector = ({ { - setAccelerationFormData({ - ...accelerationFormData, - dataSource: dataConnectionOptions[0].label, - }); + setAccelerationFormData( + producer((accData) => { + accData.dataSource = dataConnectionOptions[0].label; + accData.formErrors.dataSourceError = validateDataSource( + dataConnectionOptions[0].label + ); + }) + ); setSelectedDataConnection(dataConnectionOptions); }} - isInvalid={selectedDataConnection.length === 0} isClearable={false} + isInvalid={hasError(accelerationFormData.formErrors, 'dataSourceError')} /> { - setAccelerationFormData({ - ...accelerationFormData, - database: tableOptions[0].label, - }); - setSelectedDatabase(tableOptions); + onChange={(databaseOptions) => { + setAccelerationFormData( + producer((accData) => { + accData.database = databaseOptions[0].label; + accData.formErrors.databaseError = validateDataSource(databaseOptions[0].label); + }) + ); + setSelectedDatabase(databaseOptions); }} - isInvalid={selectedDatabase.length === 0} isClearable={false} + isInvalid={hasError(accelerationFormData.formErrors, 'databaseError')} /> { - setAccelerationFormData({ - ...accelerationFormData, - dataTable: tableOptions[0].label, - }); + setAccelerationFormData( + producer((accData) => { + accData.dataTable = tableOptions[0].label; + accData.formErrors.dataTableError = validateDataSource(tableOptions[0].label); + }) + ); setSelectedTable(tableOptions); }} - isInvalid={selectedTable.length === 0} isClearable={false} + isInvalid={hasError(accelerationFormData.formErrors, 'dataTableError')} /> 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 f61bdf99..2eb28d1d 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 @@ -17,6 +17,7 @@ import { EuiSpacer, htmlIdGenerator, } from '@elastic/eui'; +import { EuiComboBoxOptionOption } from '@opensearch-project/oui'; import React, { ChangeEvent, useEffect, useState } from 'react'; import { ACCELERATION_AGGREGRATION_FUNCTIONS } from '../../../../../common/constants'; import { @@ -43,7 +44,7 @@ export const AddColumnPopOver = ({ const [selectedFunction, setSelectedFunction] = useState([ ACCELERATION_AGGREGRATION_FUNCTIONS[0], ]); - const [selectedField, setSelectedField] = useState([]); + const [selectedField, setSelectedField] = useState([]); const [selectedAlias, setSeletedAlias] = useState(''); const resetSelectedField = () => { 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 e771c1b1..6b683423 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 @@ -15,7 +15,11 @@ import { } from '@elastic/eui'; import _ from 'lodash'; import React, { useState } from 'react'; -import { CreateAccelerationForm, DataTableFieldsType } from '../../../../../common/types'; +import { + CreateAccelerationForm, + DataTableFieldsType, + SkippingIndexRowType, +} from '../../../../../common/types'; interface AddFieldsModalProps { setIsAddModalVisible: React.Dispatch>; @@ -28,7 +32,7 @@ export const AddFieldsModal = ({ accelerationFormData, setAccelerationFormData, }: AddFieldsModalProps) => { - const [selectedFields, setSelectedFields] = useState([]); + const [selectedFields, setSelectedFields] = useState([]); const tableColumns: Array> = [ { @@ -86,7 +90,7 @@ export const AddFieldsModal = ({ skippingIndexQueryData: [ ...accelerationFormData.skippingIndexQueryData, ...selectedFields.map((x) => { - return { ...x, accelerationMethod: 'PARTITION' }; + return { ...x, accelerationMethod: 'PARTITION' } as SkippingIndexRowType; }), ], }); 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 adfa29b0..225ff06d 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 @@ -28,7 +28,7 @@ export const DeleteFieldsModal = ({ accelerationFormData, setAccelerationFormData, }: AddFieldsModalProps) => { - const [selectedFields, setSelectedFields] = useState([]); + const [selectedFields, setSelectedFields] = useState([]); const tableColumns: Array> = [ { 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 f38b120b..7a4d426f 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 @@ -13,9 +13,13 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; -import _ from 'lodash'; import React, { useEffect, useState } from 'react'; -import { CreateAccelerationForm, SkippingIndexRowType } from '../../../../../common/types'; +import { SKIPPING_INDEX_ACCELERATION_METHODS } from '../../../../../common/constants'; +import { + CreateAccelerationForm, + SkippingIndexAccMethodType, + SkippingIndexRowType, +} from '../../../../../common/types'; import { AddFieldsModal } from './add_fields_modal'; import { DeleteFieldsModal } from './delete_fields_modal'; @@ -35,12 +39,6 @@ export const SkippingIndexBuilder = ({ 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) @@ -67,13 +65,15 @@ export const SkippingIndexBuilder = ({ }; const onChangeAccelerationMethod = ( - e: { target: { value: 'PARTITION' | 'VALUE_SET' | 'MIN_MAX' } }, + e: React.ChangeEvent, updateRow: SkippingIndexRowType ) => { setAccelerationFormData({ ...accelerationFormData, - skippingIndexQueryData: _.map(accelerationFormData.skippingIndexQueryData, (row) => - row.id === updateRow.id ? { ...row, accelerationMethod: e.target.value } : row + skippingIndexQueryData: accelerationFormData.skippingIndexQueryData.map((row) => + row.id === updateRow.id + ? { ...row, accelerationMethod: e.target.value as SkippingIndexAccMethodType } + : row ), }); }; @@ -96,7 +96,7 @@ export const SkippingIndexBuilder = ({ render: (item: SkippingIndexRowType) => ( onChangeAccelerationMethod(e, item)} aria-label="Use aria labels when no actual label is in use" @@ -111,8 +111,7 @@ export const SkippingIndexBuilder = ({ onClick={() => { setAccelerationFormData({ ...accelerationFormData, - skippingIndexQueryData: _.filter( - accelerationFormData.skippingIndexQueryData, + skippingIndexQueryData: accelerationFormData.skippingIndexQueryData.filter( (o) => item.id !== o.id ), }); @@ -153,7 +152,7 @@ export const SkippingIndexBuilder = ({ return ( <> -

Skipping Index Builder

+

Skipping index definition

onTableChange(page)} hasActions={true} + error={accelerationFormData.formErrors.skippingIndexError.join('')} /> From 25449efb7465d478adc70f33f801ed56fd33f184 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Mon, 2 Oct 2023 09:12:44 -0700 Subject: [PATCH 2/8] update snapshots, add skipping validator Signed-off-by: Shenoy Pratik --- .../Main/__snapshots__/main.test.tsx.snap | 686 ++++++++++-------- .../__snapshots__/SQLPage.test.tsx.snap | 196 ++--- .../skipping_index/add_fields_modal.tsx | 24 +- .../skipping_index/skipping_index_builder.tsx | 12 +- 4 files changed, 503 insertions(+), 415 deletions(-) diff --git a/public/components/Main/__snapshots__/main.test.tsx.snap b/public/components/Main/__snapshots__/main.test.tsx.snap index 29d848bc..2f967b2b 100644 --- a/public/components/Main/__snapshots__/main.test.tsx.snap +++ b/public/components/Main/__snapshots__/main.test.tsx.snap @@ -406,61 +406,69 @@ exports[`
spec click clear button 1`] = ` class="euiSpacer euiSpacer--l" />
- -
-
- +
+
- - Clear - - - -
-
- +
+
- - Explain - - - + + + Explain + + + +
+
@@ -941,61 +949,69 @@ exports[`
spec click run button, and response causes an error 1`] = ` class="euiSpacer euiSpacer--l" />
- -
-
- +
+
- - Clear - - - -
-
- +
+
- - Explain - - - + + + Explain + + + +
+
@@ -1476,61 +1492,69 @@ exports[`
spec click run button, and response is not ok 1`] = ` class="euiSpacer euiSpacer--l" />
- -
-
- +
+
- - Clear - - - -
-
- +
+
- - Explain - - - + + + Explain + + + +
+
@@ -2077,61 +2101,69 @@ exports[`
spec click run button, and response is ok 1`] = ` class="euiSpacer euiSpacer--l" />
- -
-
- +
+
- - Clear - - - -
-
- +
+
- - Explain - - - + + + Explain + + + +
+
@@ -2674,61 +2706,69 @@ exports[`
spec click run button, response fills null and missing values class="euiSpacer euiSpacer--l" />
- -
-
- +
+
- - Clear - - - -
-
- +
+
- - Explain - - - + + + Explain + + + +
+
@@ -3212,61 +3252,69 @@ exports[`
spec click translation button, and response is ok 1`] = ` class="euiSpacer euiSpacer--l" />
- -
-
- +
+
- - Clear - - - -
-
- +
+
- - Explain - - - + + + Explain + + + +
+
@@ -3757,61 +3805,69 @@ exports[`
spec renders the component 1`] = ` class="euiSpacer euiSpacer--l" />
- -
-
- +
+
- - Clear - - - -
-
- +
+
- - Explain - - - + + + Explain + + + +
+
diff --git a/public/components/SQLPage/__snapshots__/SQLPage.test.tsx.snap b/public/components/SQLPage/__snapshots__/SQLPage.test.tsx.snap index d2fe7008..4db824cc 100644 --- a/public/components/SQLPage/__snapshots__/SQLPage.test.tsx.snap +++ b/public/components/SQLPage/__snapshots__/SQLPage.test.tsx.snap @@ -124,61 +124,69 @@ exports[` spec renders the component 1`] = ` class="euiSpacer euiSpacer--l" />
- -
-
- +
+
- - Clear - - - -
-
- +
+
- - Explain - - - + + + Explain + + + +
+
@@ -309,61 +317,69 @@ exports[` spec tests the action buttons 1`] = ` class="euiSpacer euiSpacer--l" />
- -
-
- +
+
- - Clear - - - -
-
- +
+
- - Explain - - - + + + Explain + + + +
+
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 6b683423..2fc8d883 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 @@ -13,6 +13,7 @@ import { EuiModalHeaderTitle, EuiTableFieldDataColumnType, } from '@elastic/eui'; +import producer from 'immer'; import _ from 'lodash'; import React, { useState } from 'react'; import { @@ -20,6 +21,7 @@ import { DataTableFieldsType, SkippingIndexRowType, } from '../../../../../common/types'; +import { validateSkippingIndexData } from '../../create/utils'; interface AddFieldsModalProps { setIsAddModalVisible: React.Dispatch>; @@ -85,15 +87,19 @@ export const AddFieldsModal = ({ setIsAddModalVisible(false)}>Cancel { - setAccelerationFormData({ - ...accelerationFormData, - skippingIndexQueryData: [ - ...accelerationFormData.skippingIndexQueryData, - ...selectedFields.map((x) => { - return { ...x, accelerationMethod: 'PARTITION' } as SkippingIndexRowType; - }), - ], - }); + setAccelerationFormData( + producer((accData) => { + accData.skippingIndexQueryData.push( + ...selectedFields.map((x) => { + return { ...x, accelerationMethod: 'PARTITION' } as SkippingIndexRowType; + }) + ); + accData.formErrors.skippingIndexError = validateSkippingIndexData( + accData.accelerationIndexType, + accData.skippingIndexQueryData + ); + }) + ); setIsAddModalVisible(false); }} fill 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 7a4d426f..104cdb0b 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 @@ -13,6 +13,7 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; +import producer from 'immer'; import React, { useEffect, useState } from 'react'; import { SKIPPING_INDEX_ACCELERATION_METHODS } from '../../../../../common/constants'; import { @@ -20,6 +21,7 @@ import { SkippingIndexAccMethodType, SkippingIndexRowType, } from '../../../../../common/types'; +import { validateSkippingIndexData } from '../../create/utils'; import { AddFieldsModal } from './add_fields_modal'; import { DeleteFieldsModal } from './delete_fields_modal'; @@ -139,7 +141,15 @@ export const SkippingIndexBuilder = ({ accelerationMethod: 'PARTITION', }, ]; - setAccelerationFormData({ ...accelerationFormData, skippingIndexQueryData: tableRows }); + setAccelerationFormData( + producer((accData) => { + accData.skippingIndexQueryData = tableRows; + accData.formErrors.skippingIndexError = validateSkippingIndexData( + accData.accelerationIndexType, + tableRows + ); + }) + ); } else { setAccelerationFormData({ ...accelerationFormData, skippingIndexQueryData: [] }); } From 835e6b49aa7a2cb21e94d46c266483be5a66730b Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Mon, 2 Oct 2023 09:14:55 -0700 Subject: [PATCH 3/8] update sqlpage snapshot Signed-off-by: Shenoy Pratik --- public/components/SQLPage/SQLPage.test.tsx | 30 ++++++++----------- .../__snapshots__/SQLPage.test.tsx.snap | 18 +++++++++++ 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/public/components/SQLPage/SQLPage.test.tsx b/public/components/SQLPage/SQLPage.test.tsx index 7b08b5d8..d1e7ccc3 100644 --- a/public/components/SQLPage/SQLPage.test.tsx +++ b/public/components/SQLPage/SQLPage.test.tsx @@ -3,24 +3,22 @@ * SPDX-License-Identifier: Apache-2.0 */ +import '@testing-library/jest-dom/extend-expect'; +import { fireEvent, render } from '@testing-library/react'; +import React from 'react'; +import { SQLPage } from './SQLPage'; -import React from "react"; -import "@testing-library/jest-dom/extend-expect"; -import { render, fireEvent } from "@testing-library/react"; -import { SQLPage } from "./SQLPage"; - - -describe(" spec", () => { - - it("renders the component", () => { +describe(' spec', () => { + it('renders the component', () => { render( { }} - onTranslate={() => { }} - onClear={() => { }} - updateSQLQueries={() => { }} + onRun={() => {}} + onTranslate={() => {}} + onClear={() => {}} + updateSQLQueries={() => {}} sqlTranslations={[]} sqlQuery={''} + selectedDatasource={''} /> ); expect(document.body.children[0]).toMatchSnapshot(); @@ -40,6 +38,7 @@ describe(" spec", () => { updateSQLQueries={updateSQLQueries} sqlTranslations={[]} sqlQuery={''} + selectedDatasource={'S3'} /> ); @@ -53,10 +52,5 @@ describe(" spec", () => { fireEvent.click(getByText('Explain')); expect(onTranslate).toHaveBeenCalledTimes(1); - }); - }); - - - diff --git a/public/components/SQLPage/__snapshots__/SQLPage.test.tsx.snap b/public/components/SQLPage/__snapshots__/SQLPage.test.tsx.snap index 4db824cc..96850ed5 100644 --- a/public/components/SQLPage/__snapshots__/SQLPage.test.tsx.snap +++ b/public/components/SQLPage/__snapshots__/SQLPage.test.tsx.snap @@ -381,6 +381,24 @@ exports[` spec tests the action buttons 1`] = ` +
+ +
From 2fbb601b8b7d50e6ddd1600e59c8295755c80c81 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Mon, 2 Oct 2023 14:48:57 -0700 Subject: [PATCH 4/8] add tests for acceleration create Signed-off-by: Shenoy Pratik --- package.json | 3 + public/ace-themes/sql_console.js | 7 +- .../caution_banner_callout.test.tsx.snap | 3 + .../create_acceleration.test.tsx.snap | 3 + .../create_acceleration_header.test.tsx.snap | 3 + .../__tests__/caution_banner_callout.test.tsx | 22 ++ .../__tests__/create_acceleration.test.tsx | 33 +++ .../create_acceleration_header.test.tsx | 22 ++ .../create/__tests__/utils.test.tsx | 262 ++++++++++++++++++ .../create/create_acceleration.tsx | 2 +- .../components/acceleration/create/utils.tsx | 4 +- .../covering_index/covering_index_builder.tsx | 6 +- .../materialized_view/add_column_popover.tsx | 48 ++-- .../materialized_view/column_expression.tsx | 31 ++- .../group_by_tumble_expression.tsx | 46 +-- .../materialized_view_builder.tsx | 46 ++- public/plugin.ts | 2 +- test/mocks/accelerationMock.ts | 76 +++++ 18 files changed, 556 insertions(+), 63 deletions(-) create mode 100644 public/components/acceleration/create/__tests__/__snapshots__/caution_banner_callout.test.tsx.snap create mode 100644 public/components/acceleration/create/__tests__/__snapshots__/create_acceleration.test.tsx.snap create mode 100644 public/components/acceleration/create/__tests__/__snapshots__/create_acceleration_header.test.tsx.snap create mode 100644 public/components/acceleration/create/__tests__/caution_banner_callout.test.tsx create mode 100644 public/components/acceleration/create/__tests__/create_acceleration.test.tsx create mode 100644 public/components/acceleration/create/__tests__/create_acceleration_header.test.tsx create mode 100644 public/components/acceleration/create/__tests__/utils.test.tsx create mode 100644 test/mocks/accelerationMock.ts diff --git a/package.json b/package.json index da506ddc..4bb29f82 100644 --- a/package.json +++ b/package.json @@ -27,12 +27,15 @@ }, "devDependencies": { "@testing-library/user-event": "^13.1.9", + "@types/enzyme-adapter-react-16": "^1.0.6", + "@types/react-test-renderer": "^16.9.1", "cypress": "^5.0.0", "eslint": "^6.8.0", "husky": "^4.2.5", "jest-raw-loader": "^1.0.1", "lint-staged": "^10.2.0", "mutationobserver-shim": "^0.3.3", + "jest-dom": "^4.0.0", "ts-jest": "^29.1.0" }, "resolutions": { diff --git a/public/ace-themes/sql_console.js b/public/ace-themes/sql_console.js index c841db28..b0d4898c 100644 --- a/public/ace-themes/sql_console.js +++ b/public/ace-themes/sql_console.js @@ -3,10 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ - import * as ace from 'brace'; -ace.define('ace/theme/sql_console', ['require', 'exports', 'module', 'ace/lib/dom'], function (acequire, exports, module) { +ace.define('ace/theme/sql_console', ['require', 'exports', 'module', 'ace/lib/dom'], function ( + acequire, + exports, + module +) { exports.isDark = false; exports.cssClass = 'ace-sql-console'; exports.cssText = require('../index.scss'); diff --git a/public/components/acceleration/create/__tests__/__snapshots__/caution_banner_callout.test.tsx.snap b/public/components/acceleration/create/__tests__/__snapshots__/caution_banner_callout.test.tsx.snap new file mode 100644 index 00000000..891e2965 --- /dev/null +++ b/public/components/acceleration/create/__tests__/__snapshots__/caution_banner_callout.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Acceleration callout renders acceleration flyout callout 1`] = `ReactWrapper {}`; diff --git a/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration.test.tsx.snap b/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration.test.tsx.snap new file mode 100644 index 00000000..32dc90f7 --- /dev/null +++ b/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Create acceleration flyout components renders acceleration flyout component with default options 1`] = `ReactWrapper {}`; diff --git a/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration_header.test.tsx.snap b/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration_header.test.tsx.snap new file mode 100644 index 00000000..4b3b9f03 --- /dev/null +++ b/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration_header.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Acceleration header renders acceleration flyout header 1`] = `ReactWrapper {}`; diff --git a/public/components/acceleration/create/__tests__/caution_banner_callout.test.tsx b/public/components/acceleration/create/__tests__/caution_banner_callout.test.tsx new file mode 100644 index 00000000..f3888a2b --- /dev/null +++ b/public/components/acceleration/create/__tests__/caution_banner_callout.test.tsx @@ -0,0 +1,22 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import React from 'react'; +import { CautionBannerCallout } from '../caution_banner_callout'; + +describe('Acceleration callout', () => { + configure({ adapter: new Adapter() }); + + it('renders acceleration flyout callout', async () => { + const wrapper = mount(() as React.ReactElement); + wrapper.update(); + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/acceleration/create/__tests__/create_acceleration.test.tsx b/public/components/acceleration/create/__tests__/create_acceleration.test.tsx new file mode 100644 index 00000000..1261e060 --- /dev/null +++ b/public/components/acceleration/create/__tests__/create_acceleration.test.tsx @@ -0,0 +1,33 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import React from 'react'; +import { CreateAcceleration } from '../create_acceleration'; + +describe('Create acceleration flyout components', () => { + configure({ adapter: new Adapter() }); + + it('renders acceleration flyout component with default options', async () => { + const dataSource = ''; + const resetFlyout = jest.fn(); + const updateQueries = jest.fn(); + const wrapper = mount( + ( + + ) as React.ReactElement + ); + wrapper.update(); + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/acceleration/create/__tests__/create_acceleration_header.test.tsx b/public/components/acceleration/create/__tests__/create_acceleration_header.test.tsx new file mode 100644 index 00000000..fe33575c --- /dev/null +++ b/public/components/acceleration/create/__tests__/create_acceleration_header.test.tsx @@ -0,0 +1,22 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import React from 'react'; +import { CreateAccelerationHeader } from '../create_acceleration_header'; + +describe('Acceleration header', () => { + configure({ adapter: new Adapter() }); + + it('renders acceleration flyout header', async () => { + const wrapper = mount(() as React.ReactElement); + wrapper.update(); + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/acceleration/create/__tests__/utils.test.tsx b/public/components/acceleration/create/__tests__/utils.test.tsx new file mode 100644 index 00000000..060d6385 --- /dev/null +++ b/public/components/acceleration/create/__tests__/utils.test.tsx @@ -0,0 +1,262 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + ACCELERATION_INDEX_NAME_REGEX, + ACCELERATION_S3_URL_REGEX, +} from '../../../../../common/constants'; +import { + coveringIndexDataMock, + materializedViewEmptyDataMock, + materializedViewEmptyTumbleDataMock, + materializedViewStaleDataMock, + materializedViewValidDataMock, + skippingIndexDataMock, +} from '../../../../../test/mocks/accelerationMock'; +import { + pluralizeTime, + validateCheckpointLocation, + validateCoveringIndexData, + validateDataSource, + validateDataTable, + validateDatabase, + validateIndexName, + validateMaterializedViewData, + validatePrimaryShardCount, + validateRefreshInterval, + validateReplicaCount, + validateSkippingIndexData, +} from '../utils'; + +describe('pluralizeTime', () => { + it('should return "s" for a time window greater than 1', () => { + expect(pluralizeTime(2)).toBe('s'); + expect(pluralizeTime(10)).toBe('s'); + expect(pluralizeTime(100)).toBe('s'); + }); + + it('should return an empty string for a time window of 1/0', () => { + expect(pluralizeTime(1)).toBe(''); + expect(pluralizeTime(0)).toBe(''); // form throws validation error, doesn't allow user to proceed + }); +}); + +describe('validateDataSource', () => { + it('should return an array with an error message when the dataSource is empty', () => { + expect(validateDataSource('')).toEqual(['Select a valid data source']); + expect(validateDataSource(' ')).toEqual(['Select a valid data source']); + }); + + it('should return an empty array when the dataSource is not empty', () => { + expect(validateDataSource('Some_valid_data_source')).toEqual([]); + expect(validateDataSource(' Some_valid_data_source ')).toEqual([]); + }); +}); + +describe('validateDatabase', () => { + it('should return an array with an error message when the database is empty', () => { + expect(validateDatabase('')).toEqual(['Select a valid database']); + expect(validateDatabase(' ')).toEqual(['Select a valid database']); + }); + + it('should return an empty array when the database is not empty', () => { + expect(validateDatabase('Some_valid_database')).toEqual([]); + expect(validateDatabase(' Some_valid_database ')).toEqual([]); + }); +}); + +describe('validateDataTable', () => { + it('should return an array with an error message when the dataTable is empty', () => { + expect(validateDataTable('')).toEqual(['Select a valid table']); + expect(validateDataTable(' ')).toEqual(['Select a valid table']); + }); + + it('should return an empty array when the dataTable is not empty', () => { + expect(validateDataTable('Some_valid_table')).toEqual([]); + expect(validateDataTable(' Some_valid_table ')).toEqual([]); + }); +}); + +describe('validatePrimaryShardCount', () => { + it('should return an array with an error message when primaryShardCount is less than 1', () => { + expect(validatePrimaryShardCount(0)).toEqual(['Primary shards count should be greater than 0']); + expect(validatePrimaryShardCount(-1)).toEqual([ + 'Primary shards count should be greater than 0', + ]); // form throws validation error, doesn't allow user to proceed + }); + + it('should return an empty array when primaryShardCount is greater than or equal to 1', () => { + expect(validatePrimaryShardCount(1)).toEqual([]); + expect(validatePrimaryShardCount(5)).toEqual([]); + expect(validatePrimaryShardCount(100)).toEqual([]); + }); +}); + +describe('validateReplicaCount', () => { + it('should return an array with an error message when replicaCount is less than 1', () => { + expect(validateReplicaCount(0)).toEqual(['Replica count should be greater than 0']); + expect(validateReplicaCount(-1)).toEqual(['Replica count should be greater than 0']); // form throws validation error, doesn't allow user to proceed + }); + + it('should return an empty array when replicaCount is greater than or equal to 1', () => { + expect(validateReplicaCount(1)).toEqual([]); + expect(validateReplicaCount(5)).toEqual([]); + expect(validateReplicaCount(100)).toEqual([]); + }); +}); + +describe('validateRefreshInterval', () => { + it('should return an array with an error message when refreshType is "interval" and refreshWindow is less than 1', () => { + expect(validateRefreshInterval('interval', 0)).toEqual([ + 'refresh window should be greater than 0', + ]); + expect(validateRefreshInterval('interval', -1)).toEqual([ + 'refresh window should be greater than 0', + ]); + expect(validateRefreshInterval('interval', -10)).toEqual([ + 'refresh window should be greater than 0', + ]); + }); + + it('should return an empty array when refreshType is not "interval" or when refreshWindow is greater than or equal to 1', () => { + expect(validateRefreshInterval('auto', 0)).toEqual([]); + expect(validateRefreshInterval('auto', 1)).toEqual([]); + expect(validateRefreshInterval('interval', 1)).toEqual([]); + expect(validateRefreshInterval('auto', 5)).toEqual([]); + }); +}); + +describe('validateIndexName', () => { + it('should return an array with an error message when the index name is invalid', () => { + expect(validateIndexName('_invalid')).toEqual(['Enter a valid index name']); + expect(validateIndexName('-invalid')).toEqual(['Enter a valid index name']); + expect(validateIndexName('InVal1d')).toEqual(['Enter a valid index name']); + expect(validateIndexName('invalid_with spaces')).toEqual(['Enter a valid index name']); + }); + + it('should return an empty array when the index name is valid', () => { + expect(validateIndexName('valid')).toEqual([]); + expect(validateIndexName('valid_name')).toEqual([]); + expect(validateIndexName('another-valid-name')).toEqual([]); + }); + + it('should use the ACCELERATION_INDEX_NAME_REGEX pattern to validate the index name', () => { + expect(ACCELERATION_INDEX_NAME_REGEX.test('valid_name')).toBe(true); + expect(ACCELERATION_INDEX_NAME_REGEX.test('invalid name')).toBe(false); + expect(ACCELERATION_INDEX_NAME_REGEX.test('-invalid')).toBe(false); + expect(ACCELERATION_INDEX_NAME_REGEX.test('_invalid')).toBe(false); + expect(ACCELERATION_INDEX_NAME_REGEX.test('invalid.')).toBe(false); + expect(ACCELERATION_INDEX_NAME_REGEX.test('invalid<')).toBe(false); + expect(ACCELERATION_INDEX_NAME_REGEX.test('invalid*')).toBe(false); + }); +}); + +describe('validateCheckpointLocation', () => { + it('should return an array with an error message when creating a materialized view without a checkpoint location', () => { + const materializedError = validateCheckpointLocation('materialized', undefined); + expect(materializedError).toEqual([ + 'Checkpoint location is mandatory for materialized view creation', + ]); + }); + + it('should return an array with an error message when creating a materialized view without a checkpoint location', () => { + const materializedError = validateCheckpointLocation('materialized', ''); + expect(materializedError).toEqual([ + 'Checkpoint location is mandatory for materialized view creation', + ]); + }); + + it('should return an array with an error message when the checkpoint location is not a valid S3 URL', () => { + const invalidCheckpoint = validateCheckpointLocation('skipping', 'not_a_valid_s3_url'); + expect(invalidCheckpoint).toEqual(['Enter a valid checkpoint location']); + }); + + it('should return an empty array when the checkpoint location is a valid S3 URL', () => { + const validCheckpoint = validateCheckpointLocation( + 'covering', + 's3://valid-s3-bucket/path/to/checkpoint' + ); + expect(validCheckpoint).toEqual([]); + }); + + it('should return an empty array when the checkpoint location is a valid S3A URL', () => { + const validCheckpoint = validateCheckpointLocation( + 'skipping', + 's3a://valid-s3-bucket/path/to/checkpoint' + ); + expect(validCheckpoint).toEqual([]); + }); + + it('should return an empty array when creating a materialized view with a valid checkpoint location', () => { + const validMaterializedCheckpoint = validateCheckpointLocation( + 'materialized', + 's3://valid-s3-bucket/path/to/checkpoint' + ); + expect(validMaterializedCheckpoint).toEqual([]); + }); + + it('should use the ACCELERATION_S3_URL_REGEX pattern to validate the checkpoint location', () => { + expect(ACCELERATION_S3_URL_REGEX.test('s3://valid-s3-bucket/path/to/checkpoint')).toBe(true); + expect(ACCELERATION_S3_URL_REGEX.test('s3a://valid-s3-bucket/path/to/checkpoint')).toBe(true); + expect(ACCELERATION_S3_URL_REGEX.test('https://amazon.com')).toBe(false); + expect(ACCELERATION_S3_URL_REGEX.test('http://www.amazon.com')).toBe(false); + }); +}); + +describe('validateSkippingIndexData', () => { + it('should return an array with an error message when accelerationIndexType is "skipping" and no skipping index data is provided', () => { + const error = validateSkippingIndexData('skipping', []); + expect(error).toEqual(['Add fields to the skipping index definition']); + }); + + it('should return an empty array when accelerationIndexType is not "skipping"', () => { + const noError = validateSkippingIndexData('covering', []); + expect(noError).toEqual([]); + }); + + it('should return an empty array when accelerationIndexType is "skipping" and skipping index data is provided', () => { + const noError = validateSkippingIndexData('skipping', skippingIndexDataMock); + expect(noError).toEqual([]); + }); +}); + +describe('validateCoveringIndexData', () => { + it('should return an array with an error message when accelerationIndexType is "covering" and no covering index data is provided', () => { + const error = validateCoveringIndexData('covering', []); + expect(error).toEqual(['Add fields to covering index definition']); + }); + + it('should return an empty array when accelerationIndexType is not "covering"', () => { + const noError = validateCoveringIndexData('skipping', []); + expect(noError).toEqual([]); + }); + + it('should return an empty array when accelerationIndexType is "covering" and covering index data is provided', () => { + const noError = validateCoveringIndexData('covering', coveringIndexDataMock); + expect(noError).toEqual([]); + }); +}); + +describe('validateMaterializedViewData', () => { + it('should return an array with an error message when accelerationIndexType is "materialized" and no materialized view data is provided', () => { + const error = validateMaterializedViewData('materialized', materializedViewEmptyDataMock); + expect(error).toEqual(['Add columns to materialized view definition']); + }); + + it('should return an array with an error message when accelerationIndexType is "materialized" and groupByTumbleValue is incomplete', () => { + const error = validateMaterializedViewData('materialized', materializedViewEmptyTumbleDataMock); + expect(error).toEqual(['Add a time field to tumble function in materialized view definition']); + }); + + it('should return an empty array when accelerationIndexType is not "materialized"', () => { + const noError = validateMaterializedViewData('covering', materializedViewStaleDataMock); + expect(noError).toEqual([]); + }); + + it('should return an empty array when accelerationIndexType is "materialized" and materialized view data is complete', () => { + const noError = validateMaterializedViewData('materialized', materializedViewValidDataMock); + expect(noError).toEqual([]); + }); +}); diff --git a/public/components/acceleration/create/create_acceleration.tsx b/public/components/acceleration/create/create_acceleration.tsx index f667661c..8dae138b 100644 --- a/public/components/acceleration/create/create_acceleration.tsx +++ b/public/components/acceleration/create/create_acceleration.tsx @@ -103,7 +103,7 @@ export const CreateAcceleration = ({ 0) + if (materializedViewQueryData.groupByTumbleValue.tumbleWindow < 1) return ['Add a valid time window to tumble function in materialized view definition']; return []; }; 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 0335f498..5968ca01 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 @@ -17,6 +17,7 @@ import { import React, { useState } from 'react'; import { ACCELERATION_ADD_FIELDS_TEXT } from '../../../../../common/constants'; import { CreateAccelerationForm } from '../../../../../common/types'; +import { hasError } from '../../create/utils'; interface CoveringIndexBuilderProps { accelerationFormData: CreateAccelerationForm; @@ -74,7 +75,10 @@ export const CoveringIndexBuilder = ({ value={columnsValue} isActive={isPopOverOpen} onClick={() => setIsPopOverOpen(true)} - isInvalid={columnsValue === ACCELERATION_ADD_FIELDS_TEXT} + isInvalid={ + hasError(accelerationFormData.formErrors, 'coveringIndexError') && + columnsValue === ACCELERATION_ADD_FIELDS_TEXT + } /> } isOpen={isPopOverOpen} 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 2eb28d1d..df6b276f 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 @@ -18,6 +18,7 @@ import { htmlIdGenerator, } from '@elastic/eui'; import { EuiComboBoxOptionOption } from '@opensearch-project/oui'; +import producer from 'immer'; import React, { ChangeEvent, useEffect, useState } from 'react'; import { ACCELERATION_AGGREGRATION_FUNCTIONS } from '../../../../../common/constants'; import { @@ -25,6 +26,7 @@ import { CreateAccelerationForm, MaterializedViewColumn, } from '../../../../../common/types'; +import { validateMaterializedViewData } from '../../create/utils'; interface AddColumnPopOverProps { isColumnPopOverOpen: boolean; @@ -32,6 +34,7 @@ interface AddColumnPopOverProps { columnExpressionValues: MaterializedViewColumn[]; setColumnExpressionValues: React.Dispatch>; accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; } export const AddColumnPopOver = ({ @@ -40,6 +43,7 @@ export const AddColumnPopOver = ({ columnExpressionValues, setColumnExpressionValues, accelerationFormData, + setAccelerationFormData, }: AddColumnPopOverProps) => { const [selectedFunction, setSelectedFunction] = useState([ ACCELERATION_AGGREGRATION_FUNCTIONS[0], @@ -64,6 +68,31 @@ export const AddColumnPopOver = ({ setSeletedAlias(e.target.value); }; + const onAddExpression = () => { + const newColumnExpresionValue = [ + ...columnExpressionValues, + { + id: htmlIdGenerator()(), + functionName: selectedFunction[0].label as AggregationFunctionType, + functionParam: selectedField[0].label, + fieldAlias: selectedAlias, + }, + ]; + + setAccelerationFormData( + producer((accData) => { + accData.materializedViewQueryData.columnsValues = newColumnExpresionValue; + accData.formErrors.materializedViewError = validateMaterializedViewData( + accData.accelerationIndexType, + accData.materializedViewQueryData + ); + }) + ); + + setColumnExpressionValues(newColumnExpresionValue); + setIsColumnPopOverOpen(false); + }; + useEffect(() => { resetSelectedField(); }, []); @@ -97,6 +126,7 @@ export const AddColumnPopOver = ({ options={ACCELERATION_AGGREGRATION_FUNCTIONS} selectedOptions={selectedFunction} onChange={setSelectedFunction} + isClearable={false} /> @@ -110,6 +140,7 @@ export const AddColumnPopOver = ({ ]} selectedOptions={selectedField} onChange={setSelectedField} + isClearable={false} /> @@ -120,22 +151,7 @@ export const AddColumnPopOver = ({ - { - 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 index 0da3d65b..5f9a0909 100644 --- a/public/components/acceleration/visual_editors/materialized_view/column_expression.tsx +++ b/public/components/acceleration/visual_editors/materialized_view/column_expression.tsx @@ -13,6 +13,7 @@ import { EuiFormRow, EuiPopover, } from '@elastic/eui'; +import producer from 'immer'; import _ from 'lodash'; import React, { useState } from 'react'; import { ACCELERATION_AGGREGRATION_FUNCTIONS } from '../../../../../common/constants'; @@ -21,6 +22,7 @@ import { CreateAccelerationForm, MaterializedViewColumn, } from '../../../../../common/types'; +import { validateMaterializedViewData } from '../../create/utils'; interface ColumnExpressionProps { index: number; @@ -28,6 +30,7 @@ interface ColumnExpressionProps { columnExpressionValues: MaterializedViewColumn[]; setColumnExpressionValues: React.Dispatch>; accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; } export const ColumnExpression = ({ @@ -36,6 +39,7 @@ export const ColumnExpression = ({ columnExpressionValues, setColumnExpressionValues, accelerationFormData, + setAccelerationFormData, }: ColumnExpressionProps) => { const [isFunctionPopOverOpen, setIsFunctionPopOverOpen] = useState(false); const [isAliasPopOverOpen, setIsAliasPopOverOpen] = useState(false); @@ -46,6 +50,22 @@ export const ColumnExpression = ({ setColumnExpressionValues(updatedArray); }; + const onDeleteColumnExpression = () => { + const newColumnExpresionValue = [ + ..._.filter(columnExpressionValues, (o) => o.id !== currentColumnExpressionValue.id), + ]; + setAccelerationFormData( + producer((accData) => { + accData.materializedViewQueryData.columnsValues = newColumnExpresionValue; + accData.formErrors.materializedViewError = validateMaterializedViewData( + accData.accelerationIndexType, + accData.materializedViewQueryData + ); + }) + ); + setColumnExpressionValues(newColumnExpresionValue); + }; + return ( @@ -89,6 +109,7 @@ export const ColumnExpression = ({ index ) } + isClearable={false} /> @@ -116,6 +137,7 @@ export const ColumnExpression = ({ index ) } + isClearable={false} /> @@ -162,14 +184,7 @@ export const ColumnExpression = ({ { - setColumnExpressionValues([ - ..._.filter( - columnExpressionValues, - (o) => o.id !== currentColumnExpressionValue.id - ), - ]); - }} + onClick={onDeleteColumnExpression} 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 index dc853414..e1e25339 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 @@ -14,10 +14,11 @@ import { EuiPopover, EuiSelect, } from '@elastic/eui'; -import React, { useEffect, useState } from 'react'; +import producer from 'immer'; +import React, { useState } from 'react'; import { ACCELERATION_TIME_INTERVAL } from '../../../../../common/constants'; import { CreateAccelerationForm, GroupByTumbleType } from '../../../../../common/types'; -import { pluralizeTime } from '../../create/utils'; +import { hasError, pluralizeTime, validateMaterializedViewData } from '../../create/utils'; interface GroupByTumbleExpressionProps { accelerationFormData: CreateAccelerationForm; @@ -35,29 +36,36 @@ export const GroupByTumbleExpression = ({ tumbleInterval: ACCELERATION_TIME_INTERVAL[0].value, }); + const updateGroupByStates = (newGroupByValue: GroupByTumbleType) => { + setGroupByValues(newGroupByValue); + setAccelerationFormData( + producer((accData) => { + accData.materializedViewQueryData.groupByTumbleValue = newGroupByValue; + accData.formErrors.materializedViewError = validateMaterializedViewData( + accData.accelerationIndexType, + accData.materializedViewQueryData + ); + }) + ); + }; + const onChangeTumbleWindow = (e: React.ChangeEvent) => { - setGroupByValues({ ...groupbyValues, tumbleWindow: +e.target.value }); + const newGroupByValue = { ...groupbyValues, tumbleWindow: +e.target.value }; + updateGroupByStates(newGroupByValue); }; const onChangeTumbleInterval = (e: React.ChangeEvent) => { - setGroupByValues({ ...groupbyValues, tumbleInterval: e.target.value }); + const newGroupByValue = { ...groupbyValues, tumbleInterval: e.target.value }; + updateGroupByStates(newGroupByValue); }; const onChangeTimeField = (selectedOptions: EuiComboBoxOptionOption[]) => { - if (selectedOptions.length > 0) - setGroupByValues({ ...groupbyValues, timeField: selectedOptions[0].label }); + if (selectedOptions.length > 0) { + const newGroupByValue = { ...groupbyValues, timeField: selectedOptions[0].label }; + updateGroupByStates(newGroupByValue); + } }; - useEffect(() => { - setAccelerationFormData({ - ...accelerationFormData, - materializedViewQueryData: { - ...accelerationFormData.materializedViewQueryData, - groupByTumbleValue: groupbyValues, - }, - }); - }, [groupbyValues]); - return ( setIsGroupPopOverOpen(true)} - isInvalid={groupbyValues.timeField === ''} + isInvalid={ + hasError(accelerationFormData.formErrors, 'materializedViewError') && + groupbyValues.timeField === '' + } /> } isOpen={IsGroupPopOverOpen} @@ -89,6 +100,7 @@ export const GroupByTumbleExpression = ({ .map((value) => ({ label: value.fieldName }))} selectedOptions={[{ label: groupbyValues.timeField }]} onChange={onChangeTimeField} + isClearable={false} /> 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 0f3bdeea..124b451c 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 @@ -11,9 +11,15 @@ import { EuiText, htmlIdGenerator, } from '@elastic/eui'; +import producer from 'immer'; import _ from 'lodash'; import React, { useEffect, useState } from 'react'; -import { CreateAccelerationForm, MaterializedViewColumn } from '../../../../../common/types'; +import { + AggregationFunctionType, + CreateAccelerationForm, + MaterializedViewColumn, +} from '../../../../../common/types'; +import { hasError, validateMaterializedViewData } from '../../create/utils'; import { AddColumnPopOver } from './add_column_popover'; import { ColumnExpression } from './column_expression'; import { GroupByTumbleExpression } from './group_by_tumble_expression'; @@ -36,27 +42,27 @@ export const MaterializedViewBuilder = ({ useEffect(() => { if (accelerationFormData.dataTableFields.length > 0) { - setColumnExpressionValues([ + const newColumnExpresionValue = [ { id: newColumnExpressionId, - functionName: 'count', + functionName: 'count' as AggregationFunctionType, functionParam: accelerationFormData.dataTableFields[0].fieldName, fieldAlias: 'counter1', }, - ]); + ]; + setAccelerationFormData( + producer((accData) => { + accData.materializedViewQueryData.columnsValues = newColumnExpresionValue; + accData.formErrors.materializedViewError = validateMaterializedViewData( + accData.accelerationIndexType, + accData.materializedViewQueryData + ); + }) + ); + setColumnExpressionValues(newColumnExpresionValue); } }, [accelerationFormData.dataTableFields]); - useEffect(() => { - setAccelerationFormData({ - ...accelerationFormData, - materializedViewQueryData: { - ...accelerationFormData.materializedViewQueryData, - columnsValues: columnExpressionValues, - }, - }); - }, [columnExpressionValues]); - return ( <> @@ -74,7 +80,15 @@ export const MaterializedViewBuilder = ({ - + @@ -95,6 +110,7 @@ export const MaterializedViewBuilder = ({ columnExpressionValues={columnExpressionValues} setColumnExpressionValues={setColumnExpressionValues} accelerationFormData={accelerationFormData} + setAccelerationFormData={setAccelerationFormData} /> ); })} diff --git a/public/plugin.ts b/public/plugin.ts index 10836ae1..213f4142 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -4,8 +4,8 @@ */ import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '../../../src/core/public'; -import { WorkbenchPluginSetup, WorkbenchPluginStart, AppPluginStartDependencies } from './types'; import { PLUGIN_NAME } from '../common/constants'; +import { AppPluginStartDependencies, WorkbenchPluginSetup, WorkbenchPluginStart } from './types'; export class WorkbenchPlugin implements Plugin { public setup(core: CoreSetup): WorkbenchPluginSetup { diff --git a/test/mocks/accelerationMock.ts b/test/mocks/accelerationMock.ts new file mode 100644 index 00000000..5356d610 --- /dev/null +++ b/test/mocks/accelerationMock.ts @@ -0,0 +1,76 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SkippingIndexRowType, materializedViewQueryType } from '../../common/types'; + +export const skippingIndexDataMock: SkippingIndexRowType[] = [ + { + id: '1', + fieldName: 'field1', + dataType: 'string', + accelerationMethod: 'PARTITION', + }, + { + id: '2', + fieldName: 'field2', + dataType: 'number', + accelerationMethod: 'VALUE_SET', + }, +]; + +export const coveringIndexDataMock: string[] = ['field1', 'field2', 'field3']; + +export const materializedViewEmptyDataMock = { + columnsValues: [], + groupByTumbleValue: { + timeField: '', + tumbleWindow: 0, + tumbleInterval: '', + }, +}; + +export const materializedViewEmptyTumbleDataMock: materializedViewQueryType = { + columnsValues: [ + { + id: '1', + functionName: 'count', + functionParam: 'field1', + }, + ], + groupByTumbleValue: { + timeField: '', + tumbleWindow: 0, + tumbleInterval: 'second', + }, +}; + +export const materializedViewStaleDataMock: materializedViewQueryType = { + columnsValues: [], + groupByTumbleValue: { + timeField: 'timestamp', + tumbleWindow: 10, + tumbleInterval: 'hour', + }, +}; + +export const materializedViewValidDataMock: materializedViewQueryType = { + columnsValues: [ + { + id: '1', + functionName: 'count', + functionParam: 'field1', + }, + { + id: '2', + functionName: 'sum', + functionParam: 'field2', + }, + ], + groupByTumbleValue: { + timeField: 'timestamp', + tumbleWindow: 5, + tumbleInterval: 'hour', + }, +}; From 882c27efc8876fd926b3128d39578dc92eade064 Mon Sep 17 00:00:00 2001 From: Shenoy Pratik Date: Mon, 2 Oct 2023 23:41:24 -0700 Subject: [PATCH 5/8] update acceleration builder tests Signed-off-by: Shenoy Pratik --- .../caution_banner_callout.test.tsx.snap | 38 +- .../create_acceleration.test.tsx.snap | 2474 ++++++++++++++++- .../create_acceleration_header.test.tsx.snap | 57 +- .../__tests__/caution_banner_callout.test.tsx | 10 +- .../__tests__/create_acceleration.test.tsx | 21 +- .../create_acceleration_header.test.tsx | 10 +- .../define_index_options.test.tsx.snap | 336 +++ .../index_setting_options.test.tsx.snap | 1154 ++++++++ .../index_type_selector.test.tsx.snap | 309 ++ .../source_selector.test.tsx.snap | 774 ++++++ .../__tests__/define_index_options.test.tsx | 85 + .../__tests__/index_setting_options.test.tsx | 89 + .../__tests__/index_type_selector.test.tsx | 60 + .../__tests__/source_selector.test.tsx | 62 + .../query_visual_editor.test.tsx.snap | 1126 ++++++++ .../__tests__/query_builder.test.tsx | 84 + .../__tests__/query_visual_editor.test.tsx | 116 + .../covering_index_builder.test.tsx.snap | 191 ++ .../__tests__/covering_index_builder.test.tsx | 60 + .../add_column_popover.test.tsx.snap | 84 + .../column_expression.test.tsx.snap | 175 ++ .../group_by_tumble_expression.test.tsx.snap | 65 + .../materialized_view_builder.test.tsx.snap | 318 +++ .../__tests__/add_column_popover.test.tsx | 76 + .../__tests__/column_expression.test.tsx | 78 + .../group_by_tumble_expression.test.tsx | 64 + .../materialized_view_builder.test.tsx | 64 + .../visual_editors/query_builder.tsx | 12 +- .../add_fields_modal.test.tsx.snap | 1509 ++++++++++ .../delete_fields_modal.test.tsx.snap | 2194 +++++++++++++++ .../skipping_index_builder.test.tsx.snap | 855 ++++++ .../__tests__/add_fields_modal.test.tsx | 68 + .../__tests__/delete_fields_modal.test.tsx | 68 + .../__tests__/skipping_index_builder.test.tsx | 64 + test/jest.config.js | 56 +- test/mocks/accelerationMock.ts | 304 +- yarn.lock | 854 +++--- 37 files changed, 13514 insertions(+), 450 deletions(-) create mode 100644 public/components/acceleration/selectors/__tests__/__snapshots__/define_index_options.test.tsx.snap create mode 100644 public/components/acceleration/selectors/__tests__/__snapshots__/index_setting_options.test.tsx.snap create mode 100644 public/components/acceleration/selectors/__tests__/__snapshots__/index_type_selector.test.tsx.snap create mode 100644 public/components/acceleration/selectors/__tests__/__snapshots__/source_selector.test.tsx.snap create mode 100644 public/components/acceleration/selectors/__tests__/define_index_options.test.tsx create mode 100644 public/components/acceleration/selectors/__tests__/index_setting_options.test.tsx create mode 100644 public/components/acceleration/selectors/__tests__/index_type_selector.test.tsx create mode 100644 public/components/acceleration/selectors/__tests__/source_selector.test.tsx create mode 100644 public/components/acceleration/visual_editors/__tests__/__snapshots__/query_visual_editor.test.tsx.snap create mode 100644 public/components/acceleration/visual_editors/__tests__/query_builder.test.tsx create mode 100644 public/components/acceleration/visual_editors/__tests__/query_visual_editor.test.tsx create mode 100644 public/components/acceleration/visual_editors/covering_index/__tests__/__snapshots__/covering_index_builder.test.tsx.snap create mode 100644 public/components/acceleration/visual_editors/covering_index/__tests__/covering_index_builder.test.tsx create mode 100644 public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/add_column_popover.test.tsx.snap create mode 100644 public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/column_expression.test.tsx.snap create mode 100644 public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/group_by_tumble_expression.test.tsx.snap create mode 100644 public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/materialized_view_builder.test.tsx.snap create mode 100644 public/components/acceleration/visual_editors/materialized_view/__tests__/add_column_popover.test.tsx create mode 100644 public/components/acceleration/visual_editors/materialized_view/__tests__/column_expression.test.tsx create mode 100644 public/components/acceleration/visual_editors/materialized_view/__tests__/group_by_tumble_expression.test.tsx create mode 100644 public/components/acceleration/visual_editors/materialized_view/__tests__/materialized_view_builder.test.tsx create mode 100644 public/components/acceleration/visual_editors/skipping_index/__tests__/__snapshots__/add_fields_modal.test.tsx.snap create mode 100644 public/components/acceleration/visual_editors/skipping_index/__tests__/__snapshots__/delete_fields_modal.test.tsx.snap create mode 100644 public/components/acceleration/visual_editors/skipping_index/__tests__/__snapshots__/skipping_index_builder.test.tsx.snap create mode 100644 public/components/acceleration/visual_editors/skipping_index/__tests__/add_fields_modal.test.tsx create mode 100644 public/components/acceleration/visual_editors/skipping_index/__tests__/delete_fields_modal.test.tsx create mode 100644 public/components/acceleration/visual_editors/skipping_index/__tests__/skipping_index_builder.test.tsx diff --git a/public/components/acceleration/create/__tests__/__snapshots__/caution_banner_callout.test.tsx.snap b/public/components/acceleration/create/__tests__/__snapshots__/caution_banner_callout.test.tsx.snap index 891e2965..f40a09f9 100644 --- a/public/components/acceleration/create/__tests__/__snapshots__/caution_banner_callout.test.tsx.snap +++ b/public/components/acceleration/create/__tests__/__snapshots__/caution_banner_callout.test.tsx.snap @@ -1,3 +1,39 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Acceleration callout renders acceleration flyout callout 1`] = `ReactWrapper {}`; +exports[`Acceleration callout renders acceleration flyout callout 1`] = ` +
+
+ + + Considerations for data indexing + +
+
+
+

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

+
+
+
+`; diff --git a/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration.test.tsx.snap b/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration.test.tsx.snap index 32dc90f7..589d8ab5 100644 --- a/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration.test.tsx.snap +++ b/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration.test.tsx.snap @@ -1,3 +1,2475 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Create acceleration flyout components renders acceleration flyout component with default options 1`] = `ReactWrapper {}`; +exports[`Create acceleration flyout components renders acceleration flyout component with default options 1`] = ` +Array [ + "", + +
+ + + + + + + + + + + + + +
+
+ + + Field name + + + + + + Datatype + + + + + + Acceleration method + + + + + + Delete + + +
+
+ + No items found + +
+
+ + +
+
+ +
+
+ +
+
+ + + + +
+
+
+ +
+
+ +
+
+
+ + + + } + > + Array [ + Array [ +
, + "", +
+
+ +
+
+
+
+

+ Accelerate data +

+
+
+
+
+
+ Create OpenSearch Indexes from external data connections for better performance. + + + Learn more + + + (opens in a new tab or window) + + +
+
+
+
+
+
+
+
+
+ + + Considerations for data indexing + +
+
+
+

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

+
+
+
+
+
+ Array [ +
+

+ Select data source +

+
, +
, +
+
+ Select data connection where the data you want to accelerate resides. +
+
, +
, +
+
+ +
+
+
+
+
+
+

+ Select a data source +

+
+ +
+
+
+
+ +
+
+
+
+
+ A data source has to be configured and active to be able to select it and index data from. +
+
+
, +
+
+ +
+
+
+
+
+
+

+ Select a database +

+
+ +
+
+
+
+ +
+
+
+
+
+ Select the database that contains the tables you'd like to use. +
+
+
, +
+
+ +
+
+
+
+
+
+

+ Select a table +

+
+ +
+
+
+
+ +
+
+
+
+
+ Select the Spark table that has the data you would like to index. +
+
+
, + ] +
+ Array [ +
+

+ Index settings +

+
, +
, +
+ +
+
+
+
+
+ + Skipping Index + +
+ +
+
+
+
+ +
+
+
+
+
+ Select the type of index you want to create. Each index type has benefits and costs. +
+
+
, +
+
+ +
+
+
+
+ +
+
+
+ Specify the number of primary shards for the index. Default is 5. The number of primary shards cannot be changed after the index is created. +
+
+
, +
+
+ +
+
+
+
+ +
+
+
+ Specify the number of replicas each primary shard should have. Default is 1. +
+
+
, +
+
+ +
+
+
+
+ +
+ +
+
+ +
+ +
+
+
+ Specify how often the index should refresh, which publishes the most recent changes and make them available for search. Default is set to auto refresh when data at the source changes. +
+
+
, +
+
+ +
+
+
+
+ +
+
+
+ The HDFS compatible file system location path for incremental refresh job checkpoint. +
+
+
, + ] +
+ Array [ +
+

+ Index settings +

+
, +
, +
+
+ + +
+ +
+
+
+
+ + + + +
+ +
+ +
+
+ Must be in lowercase letters. Cannot begin with underscores or hyphens. Spaces, commas, and characters :, ", *, +, /, \\, |, ?, #, >, or < are not allowed. Prefix and suffix are added to the name of generated OpenSearch index. +
+
+
, + "", + ] +
+ Array [ +
, + Array [ +
+

+ Skipping index definition +

+
, +
, +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + +
+
+ + + Field name + + + + + + Datatype + + + + + + Acceleration method + + + + + + Delete + + +
+
+ + No items found + +
+
+
+
, +
+
+ +
+
+ +
+
, + ], + ] +
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
, +
, + ], + "", + ] + , +] +`; diff --git a/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration_header.test.tsx.snap b/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration_header.test.tsx.snap index 4b3b9f03..1c099f4b 100644 --- a/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration_header.test.tsx.snap +++ b/public/components/acceleration/create/__tests__/__snapshots__/create_acceleration_header.test.tsx.snap @@ -1,3 +1,58 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Acceleration header renders acceleration flyout header 1`] = `ReactWrapper {}`; +exports[`Acceleration header renders acceleration flyout header 1`] = ` +
+
+
+

+ Accelerate data +

+
+
+
+
+
+ Create OpenSearch Indexes from external data connections for better performance. + + + Learn more + + + (opens in a new tab or window) + + +
+
+
+`; diff --git a/public/components/acceleration/create/__tests__/caution_banner_callout.test.tsx b/public/components/acceleration/create/__tests__/caution_banner_callout.test.tsx index f3888a2b..4ad06632 100644 --- a/public/components/acceleration/create/__tests__/caution_banner_callout.test.tsx +++ b/public/components/acceleration/create/__tests__/caution_banner_callout.test.tsx @@ -6,6 +6,7 @@ import { waitFor } from '@testing-library/dom'; import { configure, mount } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; import React from 'react'; import { CautionBannerCallout } from '../caution_banner_callout'; @@ -13,10 +14,15 @@ describe('Acceleration callout', () => { configure({ adapter: new Adapter() }); it('renders acceleration flyout callout', async () => { - const wrapper = mount(() as React.ReactElement); + const wrapper = mount(); wrapper.update(); await waitFor(() => { - expect(wrapper).toMatchSnapshot(); + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); }); }); }); diff --git a/public/components/acceleration/create/__tests__/create_acceleration.test.tsx b/public/components/acceleration/create/__tests__/create_acceleration.test.tsx index 1261e060..8736c5b0 100644 --- a/public/components/acceleration/create/__tests__/create_acceleration.test.tsx +++ b/public/components/acceleration/create/__tests__/create_acceleration.test.tsx @@ -6,6 +6,7 @@ import { waitFor } from '@testing-library/dom'; import { configure, mount } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; import React from 'react'; import { CreateAcceleration } from '../create_acceleration'; @@ -16,18 +17,22 @@ describe('Create acceleration flyout components', () => { const dataSource = ''; const resetFlyout = jest.fn(); const updateQueries = jest.fn(); + const wrapper = mount( - ( - - ) as React.ReactElement + ); wrapper.update(); await waitFor(() => { - expect(wrapper).toMatchSnapshot(); + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); }); }); }); diff --git a/public/components/acceleration/create/__tests__/create_acceleration_header.test.tsx b/public/components/acceleration/create/__tests__/create_acceleration_header.test.tsx index fe33575c..8f2c42a1 100644 --- a/public/components/acceleration/create/__tests__/create_acceleration_header.test.tsx +++ b/public/components/acceleration/create/__tests__/create_acceleration_header.test.tsx @@ -6,6 +6,7 @@ import { waitFor } from '@testing-library/dom'; import { configure, mount } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; import React from 'react'; import { CreateAccelerationHeader } from '../create_acceleration_header'; @@ -13,10 +14,15 @@ describe('Acceleration header', () => { configure({ adapter: new Adapter() }); it('renders acceleration flyout header', async () => { - const wrapper = mount(() as React.ReactElement); + const wrapper = mount(); wrapper.update(); await waitFor(() => { - expect(wrapper).toMatchSnapshot(); + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); }); }); }); diff --git a/public/components/acceleration/selectors/__tests__/__snapshots__/define_index_options.test.tsx.snap b/public/components/acceleration/selectors/__tests__/__snapshots__/define_index_options.test.tsx.snap new file mode 100644 index 00000000..951b7b2b --- /dev/null +++ b/public/components/acceleration/selectors/__tests__/__snapshots__/define_index_options.test.tsx.snap @@ -0,0 +1,336 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Index options acceleration components renders acceleration index options with covering index options 1`] = ` +Array [ +
+

+ Index settings +

+
, +
, +
+
+ + +
+ +
+
+
+
+ + + + + + +
+ +
+ +
+
+ Must be in lowercase letters. Cannot begin with underscores or hyphens. Spaces, commas, and characters :, ", *, +, /, \\, |, ?, #, >, or < are not allowed. Prefix and suffix are added to the name of generated OpenSearch index. +
+
+
, + "", +] +`; + +exports[`Index options acceleration components renders acceleration index options with default options 1`] = ` +Array [ +
+

+ Index settings +

+
, +
, +
+
+ + +
+ +
+
+
+
+ + + + +
+ +
+ +
+
+ Must be in lowercase letters. Cannot begin with underscores or hyphens. Spaces, commas, and characters :, ", *, +, /, \\, |, ?, #, >, or < are not allowed. Prefix and suffix are added to the name of generated OpenSearch index. +
+
+
, + "", +] +`; + +exports[`Index options acceleration components renders acceleration index options with materialized index options 1`] = ` +Array [ +
+

+ Index settings +

+
, +
, +
+
+ + +
+ +
+
+
+
+ + + + + + +
+ +
+
+
+ Must be in lowercase letters. Cannot begin with underscores or hyphens. Spaces, commas, and characters :, ", *, +, /, \\, |, ?, #, >, or < are not allowed. Prefix and suffix are added to the name of generated OpenSearch index. +
+
+
, + "", +] +`; diff --git a/public/components/acceleration/selectors/__tests__/__snapshots__/index_setting_options.test.tsx.snap b/public/components/acceleration/selectors/__tests__/__snapshots__/index_setting_options.test.tsx.snap new file mode 100644 index 00000000..a2d4908f --- /dev/null +++ b/public/components/acceleration/selectors/__tests__/__snapshots__/index_setting_options.test.tsx.snap @@ -0,0 +1,1154 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Index settings acceleration components renders acceleration index settings with default options 1`] = ` +Array [ +
+

+ Index settings +

+
, +
, +
+ +
+
+
+
+
+ + Skipping Index + +
+ +
+
+
+
+ +
+
+
+
+
+ Select the type of index you want to create. Each index type has benefits and costs. +
+
+
, +
+
+ +
+
+
+
+ +
+
+
+ Specify the number of primary shards for the index. Default is 5. The number of primary shards cannot be changed after the index is created. +
+
+
, +
+
+ +
+
+
+
+ +
+
+
+ Specify the number of replicas each primary shard should have. Default is 1. +
+
+
, +
+
+ +
+
+
+
+ +
+ +
+
+ +
+ +
+
+
+ Specify how often the index should refresh, which publishes the most recent changes and make them available for search. Default is set to auto refresh when data at the source changes. +
+
+
, +
+
+ +
+
+
+
+ +
+
+
+ The HDFS compatible file system location path for incremental refresh job checkpoint. +
+
+
, +] +`; + +exports[`Index settings acceleration components renders acceleration index settings with different options1 1`] = ` +Array [ +
+

+ Index settings +

+
, +
, +
+ +
+
+
+
+
+ + Skipping Index + +
+ +
+
+
+
+ +
+
+
+
+
+ Select the type of index you want to create. Each index type has benefits and costs. +
+
+
, +
+
+ +
+
+
+
+ +
+
+
+ Specify the number of primary shards for the index. Default is 5. The number of primary shards cannot be changed after the index is created. +
+
+
, +
+
+ +
+
+
+
+ +
+
+
+ Specify the number of replicas each primary shard should have. Default is 1. +
+
+
, +
+
+ +
+
+
+
+ +
+ +
+
+ +
+ +
+
+
+ Specify how often the index should refresh, which publishes the most recent changes and make them available for search. Default is set to auto refresh when data at the source changes. +
+
+
, +
+
+ +
+
+
+
+ +
+
+
+ The HDFS compatible file system location path for incremental refresh job checkpoint. +
+
+
, +] +`; + +exports[`Index settings acceleration components renders acceleration index settings with different options2 1`] = ` +Array [ +
+

+ Index settings +

+
, +
, +
+ +
+
+
+
+
+ + Skipping Index + +
+ +
+
+
+
+ +
+
+
+
+
+ Select the type of index you want to create. Each index type has benefits and costs. +
+
+
, +
+
+ +
+
+
+
+ +
+
+
+ Specify the number of primary shards for the index. Default is 5. The number of primary shards cannot be changed after the index is created. +
+
+
, +
+
+ +
+
+
+
+ +
+
+
+ Specify the number of replicas each primary shard should have. Default is 1. +
+
+
, +
+
+ +
+
+
+
+ +
+ +
+
+ +
+ +
+
+
+ Specify how often the index should refresh, which publishes the most recent changes and make them available for search. Default is set to auto refresh when data at the source changes. +
+
+
, +
+
+ +
+
+
+
+ +
+
+
+ The HDFS compatible file system location path for incremental refresh job checkpoint. +
+
+
, +] +`; diff --git a/public/components/acceleration/selectors/__tests__/__snapshots__/index_type_selector.test.tsx.snap b/public/components/acceleration/selectors/__tests__/__snapshots__/index_type_selector.test.tsx.snap new file mode 100644 index 00000000..6a41fd85 --- /dev/null +++ b/public/components/acceleration/selectors/__tests__/__snapshots__/index_type_selector.test.tsx.snap @@ -0,0 +1,309 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Index type selector components renders type selector with default options 1`] = ` +
+ +
+
+
+
+
+ + Skipping Index + +
+ +
+
+
+
+ +
+
+
+
+
+ Select the type of index you want to create. Each index type has benefits and costs. +
+
+
+`; + +exports[`Index type selector components renders type selector with different options 1`] = ` +
+ +
+
+
+
+
+ + Skipping Index + +
+ +
+
+
+
+ +
+
+
+
+
+ Select the type of index you want to create. Each index type has benefits and costs. +
+
+
+`; diff --git a/public/components/acceleration/selectors/__tests__/__snapshots__/source_selector.test.tsx.snap b/public/components/acceleration/selectors/__tests__/__snapshots__/source_selector.test.tsx.snap new file mode 100644 index 00000000..bf2a7962 --- /dev/null +++ b/public/components/acceleration/selectors/__tests__/__snapshots__/source_selector.test.tsx.snap @@ -0,0 +1,774 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Source selector components renders source selector with default options 1`] = ` +Array [ +
+

+ Select data source +

+
, +
, +
+
+ Select data connection where the data you want to accelerate resides. +
+
, +
, +
+
+ +
+
+
+
+
+
+

+ Select a data source +

+
+ +
+
+
+
+ +
+
+
+
+
+ A data source has to be configured and active to be able to select it and index data from. +
+
+
, +
+
+ +
+
+
+
+
+
+

+ Select a database +

+
+ +
+
+
+
+ +
+
+
+
+
+ Select the database that contains the tables you'd like to use. +
+
+
, +
+
+ +
+
+
+
+
+
+

+ Select a table +

+
+ +
+
+
+
+ +
+
+
+
+
+ Select the Spark table that has the data you would like to index. +
+
+
, +] +`; + +exports[`Source selector components renders source selector with different options 1`] = ` +Array [ +
+

+ Select data source +

+
, +
, +
+
+ Select data connection where the data you want to accelerate resides. +
+
, +
, +
+
+ +
+
+
+
+
+
+

+ Select a data source +

+
+ +
+
+
+
+ +
+
+
+
+
+ A data source has to be configured and active to be able to select it and index data from. +
+
+
, +
+
+ +
+
+
+
+
+
+

+ Select a database +

+
+ +
+
+
+
+ +
+
+
+
+
+ Select the database that contains the tables you'd like to use. +
+
+
, +
+
+ +
+
+
+
+
+
+

+ Select a table +

+
+ +
+
+
+
+ +
+
+
+
+
+ Select the Spark table that has the data you would like to index. +
+
+
, +] +`; diff --git a/public/components/acceleration/selectors/__tests__/define_index_options.test.tsx b/public/components/acceleration/selectors/__tests__/define_index_options.test.tsx new file mode 100644 index 00000000..4d8cf515 --- /dev/null +++ b/public/components/acceleration/selectors/__tests__/define_index_options.test.tsx @@ -0,0 +1,85 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; +import React from 'react'; +import { CreateAccelerationForm } from '../../../../../common/types'; +import { createAccelerationEmptyDataMock } from '../../../../../test/mocks/accelerationMock'; +import { DefineIndexOptions } from '../define_index_options'; + +describe('Index options acceleration components', () => { + configure({ adapter: new Adapter() }); + + it('renders acceleration index options with default options', async () => { + const accelerationFormData = createAccelerationEmptyDataMock; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders acceleration index options with covering index options', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + accelerationIndexType: 'covering', + accelerationIndexName: 'covering-idx', + }; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders acceleration index options with materialized index options', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + accelerationIndexType: 'materialized', + accelerationIndexName: 'mv_metrics', + }; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/acceleration/selectors/__tests__/index_setting_options.test.tsx b/public/components/acceleration/selectors/__tests__/index_setting_options.test.tsx new file mode 100644 index 00000000..a8094b19 --- /dev/null +++ b/public/components/acceleration/selectors/__tests__/index_setting_options.test.tsx @@ -0,0 +1,89 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; +import React from 'react'; +import { CreateAccelerationForm } from '../../../../../common/types'; +import { createAccelerationEmptyDataMock } from '../../../../../test/mocks/accelerationMock'; +import { IndexSettingOptions } from '../index_setting_options'; + +describe('Index settings acceleration components', () => { + configure({ adapter: new Adapter() }); + + it('renders acceleration index settings with default options', async () => { + const accelerationFormData = createAccelerationEmptyDataMock; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders acceleration index settings with different options1', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + primaryShardsCount: 1, + replicaShardsCount: 5, + refreshType: 'auto', + }; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders acceleration index settings with different options2', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + primaryShardsCount: 5, + replicaShardsCount: 1, + refreshType: 'interval', + refreshIntervalOptions: { refreshWindow: 1, refreshInterval: 'second' }, + checkpointLocation: 's3://test/url', + }; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/acceleration/selectors/__tests__/index_type_selector.test.tsx b/public/components/acceleration/selectors/__tests__/index_type_selector.test.tsx new file mode 100644 index 00000000..ba2e082c --- /dev/null +++ b/public/components/acceleration/selectors/__tests__/index_type_selector.test.tsx @@ -0,0 +1,60 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; +import React from 'react'; +import { CreateAccelerationForm } from '../../../../../common/types'; +import { createAccelerationEmptyDataMock } from '../../../../../test/mocks/accelerationMock'; +import { IndexTypeSelector } from '../index_type_selector'; + +describe('Index type selector components', () => { + configure({ adapter: new Adapter() }); + + it('renders type selector with default options', async () => { + const accelerationFormData = createAccelerationEmptyDataMock; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders type selector with different options', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + accelerationIndexType: 'covering', + }; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/acceleration/selectors/__tests__/source_selector.test.tsx b/public/components/acceleration/selectors/__tests__/source_selector.test.tsx new file mode 100644 index 00000000..f5d1ff57 --- /dev/null +++ b/public/components/acceleration/selectors/__tests__/source_selector.test.tsx @@ -0,0 +1,62 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; +import React from 'react'; +import { CreateAccelerationForm } from '../../../../../common/types'; +import { createAccelerationEmptyDataMock } from '../../../../../test/mocks/accelerationMock'; +import { AccelerationDataSourceSelector } from '../source_selector'; + +describe('Source selector components', () => { + configure({ adapter: new Adapter() }); + + it('renders source selector with default options', async () => { + const accelerationFormData = createAccelerationEmptyDataMock; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders source selector with different options', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + dataSource: 'ds', + database: 'db', + dataTable: 'tb', + }; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/acceleration/visual_editors/__tests__/__snapshots__/query_visual_editor.test.tsx.snap b/public/components/acceleration/visual_editors/__tests__/__snapshots__/query_visual_editor.test.tsx.snap new file mode 100644 index 00000000..8659a435 --- /dev/null +++ b/public/components/acceleration/visual_editors/__tests__/__snapshots__/query_visual_editor.test.tsx.snap @@ -0,0 +1,1126 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Visual builder components renders visual builder with covering index options 1`] = ` +Array [ +
, + Array [ +
+

+ Covering index definition +

+
, +
, +
+
+ + + CREATE INDEX + + + + cv-idx + + +
+
+ + + [IF NOT EXISTS] + + + +
+
+ + + ON + + + + .. + + +
+
+ +
+
+
+
, + ], +] +`; + +exports[`Visual builder components renders visual builder with default options 1`] = ` +Array [ +
, + Array [ +
+

+ Skipping index definition +

+
, +
, +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + +
+
+ + + Field name + + + + + + Datatype + + + + + + Acceleration method + + + + + + Delete + + +
+
+ + No items found + +
+
+
+
, +
+
+ +
+
+ +
+
, + ], +] +`; + +exports[`Visual builder components renders visual builder with materialized view options 1`] = ` +Array [ +
, + Array [ +
+

+ Materialized view definition +

+
, +
, + + + CREATE MATERIALIZED VIEW + + + + ..skipping + + , +
+ + + [IF NOT EXISTS] + + + +
, +
+
+ + + AS SELECT + + + +
+
+
+
+ +
+
+
+
, +
, +
, + + + FROM + + + + .. + + , +
, +
+
+
+ +
+
+
, + ], +] +`; + +exports[`Visual builder components renders visual builder with skipping index options 1`] = ` +Array [ +
, + Array [ +
+

+ Skipping index definition +

+
, +
, +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + Field name + + + + + + Datatype + + + + + + Acceleration method + + + + + + Delete + + +
+
+ Field name +
+
+ + field1 + +
+
+
+ Datatype +
+
+ + string + +
+
+
+ Acceleration method +
+
+
+
+ +
+ + + +
+
+
+
+
+
+ Delete +
+
+ +
+
+
+ Field name +
+
+ + field2 + +
+
+
+ Datatype +
+
+ + number + +
+
+
+ Acceleration method +
+
+
+
+ +
+ + + +
+
+
+
+
+
+ Delete +
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
, +
+
+ +
+
+ +
+
, + ], +] +`; diff --git a/public/components/acceleration/visual_editors/__tests__/query_builder.test.tsx b/public/components/acceleration/visual_editors/__tests__/query_builder.test.tsx new file mode 100644 index 00000000..2aca0b68 --- /dev/null +++ b/public/components/acceleration/visual_editors/__tests__/query_builder.test.tsx @@ -0,0 +1,84 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + coveringIndexBuilderMock1, + coveringIndexBuilderMock2, + coveringIndexBuilderMockResult1, + coveringIndexBuilderMockResult2, + indexOptionsMock1, + indexOptionsMock2, + indexOptionsMock3, + indexOptionsMockResult1, + indexOptionsMockResult2, + indexOptionsMockResult3, + materializedViewBuilderMock1, + materializedViewBuilderMock2, + materializedViewBuilderMockResult1, + materializedViewBuilderMockResult2, + skippingIndexBuilderMock1, + skippingIndexBuilderMock2, + skippingIndexBuilderMockResult1, + skippingIndexBuilderMockResult2, +} from '../../../../../test/mocks/accelerationMock'; +import { + buildIndexOptions, + coveringIndexQueryBuilder, + materializedQueryViewBuilder, + skippingIndexQueryBuilder, +} from '../query_builder'; + +describe('buildIndexOptions', () => { + it('should build index options with auto refresh', () => { + const indexOptions = buildIndexOptions(indexOptionsMock1); + expect(indexOptions).toEqual(indexOptionsMockResult1); + }); + + it('should build index options with interval refresh', () => { + const indexOptions = buildIndexOptions(indexOptionsMock2); + expect(indexOptions).toEqual(indexOptionsMockResult2); + }); + + it('should build index options with checkpoint location', () => { + const indexOptions = buildIndexOptions(indexOptionsMock3); + expect(indexOptions).toEqual(indexOptionsMockResult3); + }); + + describe('skippingIndexQueryBuilder', () => { + it('should build skipping index query as expected with interval refresh', () => { + const result = skippingIndexQueryBuilder(skippingIndexBuilderMock1); + expect(result).toEqual(skippingIndexBuilderMockResult1); + }); + + it('should build skipping index query as expected with auto refresh', () => { + const result = skippingIndexQueryBuilder(skippingIndexBuilderMock2); + expect(result).toEqual(skippingIndexBuilderMockResult2); + }); + }); + + describe('coveringIndexQueryBuilder', () => { + it('should build covering index query as expected with interval refresh', () => { + const result = coveringIndexQueryBuilder(coveringIndexBuilderMock1); + expect(result).toEqual(coveringIndexBuilderMockResult1); + }); + + it('should build covering index query as expected with auto refresh', () => { + const result = coveringIndexQueryBuilder(coveringIndexBuilderMock2); + expect(result).toEqual(coveringIndexBuilderMockResult2); + }); + }); + + describe('materializedQueryViewBuilder', () => { + it('should build materialized view query as expected with interval refresh', () => { + const result = materializedQueryViewBuilder(materializedViewBuilderMock1); + expect(result).toEqual(materializedViewBuilderMockResult1); + }); + + it('should build materialized view query as expected with auto refresh', () => { + const result = materializedQueryViewBuilder(materializedViewBuilderMock2); + expect(result).toEqual(materializedViewBuilderMockResult2); + }); + }); +}); diff --git a/public/components/acceleration/visual_editors/__tests__/query_visual_editor.test.tsx b/public/components/acceleration/visual_editors/__tests__/query_visual_editor.test.tsx new file mode 100644 index 00000000..38513b86 --- /dev/null +++ b/public/components/acceleration/visual_editors/__tests__/query_visual_editor.test.tsx @@ -0,0 +1,116 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; +import React from 'react'; +import { CreateAccelerationForm } from '../../../../../common/types'; +import { + coveringIndexDataMock, + createAccelerationEmptyDataMock, + materializedViewValidDataMock, + skippingIndexDataMock, +} from '../../../../../test/mocks/accelerationMock'; +import { QueryVisualEditor } from '../query_visual_editor'; + +describe('Visual builder components', () => { + configure({ adapter: new Adapter() }); + + it('renders visual builder with default options', async () => { + const accelerationFormData = createAccelerationEmptyDataMock; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders visual builder with skipping index options', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + accelerationIndexName: 'skipping', + accelerationIndexType: 'skipping', + skippingIndexQueryData: skippingIndexDataMock, + }; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders visual builder with covering index options', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + accelerationIndexName: 'cv-idx', + accelerationIndexType: 'covering', + coveringIndexQueryData: coveringIndexDataMock, + }; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders visual builder with materialized view options', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + accelerationIndexType: 'materialized', + materializedViewQueryData: materializedViewValidDataMock, + }; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/acceleration/visual_editors/covering_index/__tests__/__snapshots__/covering_index_builder.test.tsx.snap b/public/components/acceleration/visual_editors/covering_index/__tests__/__snapshots__/covering_index_builder.test.tsx.snap new file mode 100644 index 00000000..3d237119 --- /dev/null +++ b/public/components/acceleration/visual_editors/covering_index/__tests__/__snapshots__/covering_index_builder.test.tsx.snap @@ -0,0 +1,191 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Covering index builder components renders covering index builder with different options 1`] = ` +Array [ +
+

+ Covering index definition +

+
, +
, +
+
+ + + CREATE INDEX + + + + skipping + + +
+
+ + + [IF NOT EXISTS] + + + +
+
+ + + ON + + + + .. + + +
+
+ +
+
+
+
, +] +`; + +exports[`Covering index builder components renders covering index builder with default options 1`] = ` +Array [ +
+

+ Covering index definition +

+
, +
, +
+
+ + + CREATE INDEX + + + + skipping + + +
+
+ + + [IF NOT EXISTS] + + + +
+
+ + + ON + + + + .. + + +
+
+ +
+
+
+
, +] +`; diff --git a/public/components/acceleration/visual_editors/covering_index/__tests__/covering_index_builder.test.tsx b/public/components/acceleration/visual_editors/covering_index/__tests__/covering_index_builder.test.tsx new file mode 100644 index 00000000..18a73bae --- /dev/null +++ b/public/components/acceleration/visual_editors/covering_index/__tests__/covering_index_builder.test.tsx @@ -0,0 +1,60 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; +import React from 'react'; +import { CreateAccelerationForm } from '../../../../../../common/types'; +import { createAccelerationEmptyDataMock } from '../../../../../../test/mocks/accelerationMock'; +import { CoveringIndexBuilder } from '../covering_index_builder'; + +describe('Covering index builder components', () => { + configure({ adapter: new Adapter() }); + + it('renders covering index builder with default options', async () => { + const accelerationFormData = createAccelerationEmptyDataMock; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders covering index builder with different options', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + coveringIndexQueryData: ['field1', 'field2', 'field3'], + }; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/add_column_popover.test.tsx.snap b/public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/add_column_popover.test.tsx.snap new file mode 100644 index 00000000..ffb0224d --- /dev/null +++ b/public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/add_column_popover.test.tsx.snap @@ -0,0 +1,84 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Column popover components in materialized view renders column popover components in materialized view with default options 1`] = ` +
+
+ +
+
+`; + +exports[`Column popover components in materialized view renders column popover components in materialized view with different options 1`] = ` +
+
+ +
+
+`; diff --git a/public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/column_expression.test.tsx.snap b/public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/column_expression.test.tsx.snap new file mode 100644 index 00000000..61725510 --- /dev/null +++ b/public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/column_expression.test.tsx.snap @@ -0,0 +1,175 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Column expression components in materialized view renders column expression components in materialized view with default options 1`] = ` +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+`; + +exports[`Column expression components in materialized view renders column expression components in materialized view with different options 1`] = ` +
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+`; diff --git a/public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/group_by_tumble_expression.test.tsx.snap b/public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/group_by_tumble_expression.test.tsx.snap new file mode 100644 index 00000000..235e817d --- /dev/null +++ b/public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/group_by_tumble_expression.test.tsx.snap @@ -0,0 +1,65 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Group by components in materialized view renders group by components in materialized view with default options 1`] = ` +
+
+
+ +
+
+
+`; + +exports[`Group by components in materialized view renders group by components in materialized view with different options 1`] = ` +
+
+
+ +
+
+
+`; diff --git a/public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/materialized_view_builder.test.tsx.snap b/public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/materialized_view_builder.test.tsx.snap new file mode 100644 index 00000000..75955730 --- /dev/null +++ b/public/components/acceleration/visual_editors/materialized_view/__tests__/__snapshots__/materialized_view_builder.test.tsx.snap @@ -0,0 +1,318 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Builder components in materialized view renders builder components in materialized view with default options 1`] = ` +Array [ +
+

+ Materialized view definition +

+
, +
, + + + CREATE MATERIALIZED VIEW + + + + ..skipping + + , +
+ + + [IF NOT EXISTS] + + + +
, +
+
+ + + AS SELECT + + + +
+
+
+
+ +
+
+
+
, +
, +
, + + + FROM + + + + .. + + , +
, +
+
+
+ +
+
+
, +] +`; + +exports[`Builder components in materialized view renders builder components in materialized view with different options 1`] = ` +Array [ +
+

+ Materialized view definition +

+
, +
, + + + CREATE MATERIALIZED VIEW + + + + ..skipping + + , +
+ + + [IF NOT EXISTS] + + + +
, +
+
+ + + AS SELECT + + + +
+
+
+
+ +
+
+
+
, +
, +
, + + + FROM + + + + .. + + , +
, +
+
+
+ +
+
+
, +] +`; diff --git a/public/components/acceleration/visual_editors/materialized_view/__tests__/add_column_popover.test.tsx b/public/components/acceleration/visual_editors/materialized_view/__tests__/add_column_popover.test.tsx new file mode 100644 index 00000000..56c40581 --- /dev/null +++ b/public/components/acceleration/visual_editors/materialized_view/__tests__/add_column_popover.test.tsx @@ -0,0 +1,76 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; +import React from 'react'; +import { CreateAccelerationForm } from '../../../../../../common/types'; +import { + createAccelerationEmptyDataMock, + materializedViewValidDataMock, +} from '../../../../../../test/mocks/accelerationMock'; +import { AddColumnPopOver } from '../add_column_popover'; + +describe('Column popover components in materialized view', () => { + configure({ adapter: new Adapter() }); + + it('renders column popover components in materialized view with default options', async () => { + const accelerationFormData = createAccelerationEmptyDataMock; + const setAccelerationFormData = jest.fn(); + const setIsColumnPopOverOpen = jest.fn(); + const setColumnExpressionValues = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders column popover components in materialized view with different options', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + accelerationIndexType: 'materialized', + materializedViewQueryData: materializedViewValidDataMock, + }; + const setAccelerationFormData = jest.fn(); + const setIsColumnPopOverOpen = jest.fn(); + const setColumnExpressionValues = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/acceleration/visual_editors/materialized_view/__tests__/column_expression.test.tsx b/public/components/acceleration/visual_editors/materialized_view/__tests__/column_expression.test.tsx new file mode 100644 index 00000000..4ce888f5 --- /dev/null +++ b/public/components/acceleration/visual_editors/materialized_view/__tests__/column_expression.test.tsx @@ -0,0 +1,78 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; +import React from 'react'; +import { CreateAccelerationForm } from '../../../../../../common/types'; +import { + createAccelerationEmptyDataMock, + materializedViewValidDataMock, +} from '../../../../../../test/mocks/accelerationMock'; +import { ColumnExpression } from '../column_expression'; + +describe('Column expression components in materialized view', () => { + configure({ adapter: new Adapter() }); + + it('renders column expression components in materialized view with default options', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + accelerationIndexType: 'materialized', + materializedViewQueryData: materializedViewValidDataMock, + }; + const setAccelerationFormData = jest.fn(); + const setColumnExpressionValues = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders column expression components in materialized view with different options', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + accelerationIndexType: 'materialized', + materializedViewQueryData: materializedViewValidDataMock, + }; + const setAccelerationFormData = jest.fn(); + const setColumnExpressionValues = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/acceleration/visual_editors/materialized_view/__tests__/group_by_tumble_expression.test.tsx b/public/components/acceleration/visual_editors/materialized_view/__tests__/group_by_tumble_expression.test.tsx new file mode 100644 index 00000000..1a94eb3f --- /dev/null +++ b/public/components/acceleration/visual_editors/materialized_view/__tests__/group_by_tumble_expression.test.tsx @@ -0,0 +1,64 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; +import React from 'react'; +import { CreateAccelerationForm } from '../../../../../../common/types'; +import { + createAccelerationEmptyDataMock, + materializedViewValidDataMock, +} from '../../../../../../test/mocks/accelerationMock'; +import { GroupByTumbleExpression } from '../group_by_tumble_expression'; + +describe('Group by components in materialized view', () => { + configure({ adapter: new Adapter() }); + + it('renders group by components in materialized view with default options', async () => { + const accelerationFormData = createAccelerationEmptyDataMock; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders group by components in materialized view with different options', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + accelerationIndexType: 'materialized', + materializedViewQueryData: materializedViewValidDataMock, + }; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/acceleration/visual_editors/materialized_view/__tests__/materialized_view_builder.test.tsx b/public/components/acceleration/visual_editors/materialized_view/__tests__/materialized_view_builder.test.tsx new file mode 100644 index 00000000..2075c6b0 --- /dev/null +++ b/public/components/acceleration/visual_editors/materialized_view/__tests__/materialized_view_builder.test.tsx @@ -0,0 +1,64 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; +import React from 'react'; +import { CreateAccelerationForm } from '../../../../../../common/types'; +import { + createAccelerationEmptyDataMock, + materializedViewValidDataMock, +} from '../../../../../../test/mocks/accelerationMock'; +import { MaterializedViewBuilder } from '../materialized_view_builder'; + +describe('Builder components in materialized view', () => { + configure({ adapter: new Adapter() }); + + it('renders builder components in materialized view with default options', async () => { + const accelerationFormData = createAccelerationEmptyDataMock; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); + + it('renders builder components in materialized view with different options', async () => { + const accelerationFormData: CreateAccelerationForm = { + ...createAccelerationEmptyDataMock, + accelerationIndexType: 'materialized', + materializedViewQueryData: materializedViewValidDataMock, + }; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/acceleration/visual_editors/query_builder.tsx b/public/components/acceleration/visual_editors/query_builder.tsx index 5be5e6df..49d4482f 100644 --- a/public/components/acceleration/visual_editors/query_builder.tsx +++ b/public/components/acceleration/visual_editors/query_builder.tsx @@ -12,7 +12,7 @@ import { import { pluralizeTime } from '../create/utils'; /* Add index options to query */ -const buildIndexOptions = (accelerationformData: CreateAccelerationForm) => { +export const buildIndexOptions = (accelerationformData: CreateAccelerationForm) => { const { primaryShardsCount, replicaShardsCount, @@ -71,7 +71,7 @@ const buildSkippingIndexColumns = (skippingIndexQueryData: SkippingIndexRowType[ * index_settings = '{"number_of_shards":9,"number_of_replicas":2}' * ) */ -const skippingIndexQueryBuilder = (accelerationformData: CreateAccelerationForm) => { +export const skippingIndexQueryBuilder = (accelerationformData: CreateAccelerationForm) => { const { dataSource, database, dataTable, skippingIndexQueryData } = accelerationformData; const codeQuery = `CREATE SKIPPING INDEX @@ -107,7 +107,7 @@ const buildCoveringIndexColumns = (coveringIndexQueryData: string[]) => { * index_settings = '{"number_of_shards":9,"number_of_replicas":2}' * ) */ -const coveringIndexQueryBuilder = (accelerationformData: CreateAccelerationForm) => { +export const coveringIndexQueryBuilder = (accelerationformData: CreateAccelerationForm) => { const { dataSource, database, @@ -130,8 +130,8 @@ const buildMaterializedViewColumns = (columnsValues: MaterializedViewColumn[]) = return columnsValues .map( (column) => - ` ${column.functionName}(${column.functionParam}) ${ - column.fieldAlias && `AS ${column.fieldAlias}` + ` ${column.functionName}(${column.functionParam})${ + column.fieldAlias ? ` AS ${column.fieldAlias}` : `` }` ) .join(', \n'); @@ -161,7 +161,7 @@ const buildTumbleValue = (GroupByTumbleValue: GroupByTumbleType) => { * index_settings = '{"number_of_shards":9,"number_of_replicas":2}' * ) */ -const materializedQueryViewBuilder = (accelerationformData: CreateAccelerationForm) => { +export const materializedQueryViewBuilder = (accelerationformData: CreateAccelerationForm) => { const { dataSource, database, diff --git a/public/components/acceleration/visual_editors/skipping_index/__tests__/__snapshots__/add_fields_modal.test.tsx.snap b/public/components/acceleration/visual_editors/skipping_index/__tests__/__snapshots__/add_fields_modal.test.tsx.snap new file mode 100644 index 00000000..f2f42748 --- /dev/null +++ b/public/components/acceleration/visual_editors/skipping_index/__tests__/__snapshots__/add_fields_modal.test.tsx.snap @@ -0,0 +1,1509 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Add fields modal in skipping index renders add fields modal in skipping index with default options 1`] = ` + +