Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add materialized view visual builder and query builders #129

Merged
merged 2 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions common/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
export const PLUGIN_ID = 'queryWorkbenchDashboards';
export const PLUGIN_NAME = 'Query Workbench';
export const OPENSEARCH_ACC_DOCUMENTATION_URL = 'https://opensearch.org/docs/latest';
export const ACC_INDEX_TYPE_DOCUMENTATION_URL = 'https://opensearch.org/docs/latest';
ps48 marked this conversation as resolved.
Show resolved Hide resolved

export const ACCELERATION_INDEX_TYPES = [
{ label: 'Skipping Index', value: 'skipping' },
Expand All @@ -14,9 +15,33 @@ export const ACCELERATION_INDEX_TYPES = [
];

export const ACCELERATION_AGGREGRATION_FUNCTIONS = [
{ label: 'count', value: 'count' },
{ label: 'sum', value: 'sum' },
{ label: 'avg', value: 'avg' },
{ label: 'max', value: 'max' },
{ label: 'min', value: 'min' },
{ label: 'count' },
{ label: 'sum' },
{ label: 'avg' },
{ label: 'max' },
{ label: 'min' },
];

export const ACCELERATION_TIME_INTERVAL = [
{ text: 'millisecond(s)', value: 'millisecond' },
{ text: 'second(s)', value: 'second' },
{ text: 'hour(s)', value: 'hour' },
{ text: 'day(s)', value: 'day' },
{ text: 'week(s)', value: 'week' },
];

export const ACCELERATION_ADD_FIELDS_TEXT = '(add fields here)';

export const ACCELERATION_INDEX_NAME_INFO = `All OpenSearch acceleration indices have a naming format of pattern: \`prefix_<index name>_suffix\`. They share a common prefix structure, which is \`flint_<data source name>_<database name>_<table name>_\`. Additionally, they may have a suffix that varies based on the index type.
##### Skipping Index
- For 'Skipping' indices, a fixed index name 'skipping' is used, and this name cannot be modified by the user. The suffix added to this type is \`_index\`.
- An example of a 'Skipping' index name would be: \`flint_mydatasource_mydb_mytable_skipping_index\`.
##### Covering Index
- 'Covering' indices allow users to specify their index name. The suffix added to this type is \`_index\`.
- For instance, a 'Covering' index name could be: \`flint_mydatasource_mydb_mytable_myindexname_index\`.
##### Materialized View Index
- 'Materialized View' indices also enable users to define their index name, but they do not have a suffix.
- An example of a 'Materialized View' index name might look like: \`flint_mydatasource_mydb_mytable_myindexname\`.
##### Note:
- All user given index names must be in lowercase letters. Cannot begin with underscores or hyphens. Spaces, commas, and characters :, ", *, +, /, \, |, ?, #, >, or < are not allowed.
ps48 marked this conversation as resolved.
Show resolved Hide resolved
ps48 marked this conversation as resolved.
Show resolved Hide resolved
`;
34 changes: 27 additions & 7 deletions common/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
* SPDX-License-Identifier: Apache-2.0
*/

export type AggregationFunctionType = 'count' | 'sum' | 'avg' | 'max' | 'min';

export interface MaterializedViewColumn {
id: string;
functionName: 'count' | 'sum' | 'avg' | 'min' | 'max';
functionName: AggregationFunctionType;
ps48 marked this conversation as resolved.
Show resolved Hide resolved
functionParam: string;
fieldAlias?: string;
}
Expand All @@ -23,19 +25,37 @@ export interface DataTableFieldsType {
dataType: string;
}

export interface RefreshIntervalType {
refreshWindow: number;
refreshInterval: string;
}

export type AccelerationIndexType = 'skipping' | 'covering' | 'materialized';

export interface GroupByTumbleType {
timeField: string;
tumbleWindow: number;
tumbleInterval: string;
}

export interface materializedViewQueryType {
columnsValues: MaterializedViewColumn[];
groupByTumbleValue: GroupByTumbleType;
}

export interface CreateAccelerationForm {
dataSource: string;
database: string;
dataTable: string;
dataTableFields: DataTableFieldsType[];
accelerationIndexType: 'skipping' | 'covering' | 'materialized';
queryBuilderType: 'visual' | 'code';
accelerationIndexType: AccelerationIndexType;
skippingIndexQueryData: SkippingIndexRowType[];
coveringIndexQueryData: string;
materializedViewQueryData: string;
coveringIndexQueryData: string[];
materializedViewQueryData: materializedViewQueryType;
accelerationIndexName: string;
accelerationIndexAlias: string;
primaryShardsCount: number;
replicaShardsCount: number;
refreshType: 'interval' | 'auto';
refreshIntervalSeconds: string | undefined;
checkpointLocation: string | undefined;
refreshIntervalOptions: RefreshIntervalType | undefined;
}
27 changes: 19 additions & 8 deletions public/components/acceleration/create/create_acceleration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import React, { useState } from 'react';
import { CreateAccelerationHeader } from './create_acceleration_header';
import { CautionBannerCallout } from './caution_banner_callout';
import { AccelerationDataSourceSelector } from '../selectors/source_selector';
import { IndexTypeSelector } from '../selectors/index_type_selector';
import { CreateAccelerationForm } from '../../../../common/types/';
import { CreateAccelerationForm, materializedViewQueryType } from '../../../../common/types/';
import { QueryVisualEditor } from '../visual_editors/query_visual_editor';
import { accelerationQueryBuilder } from '../visual_editors/query_builder';
import { IndexSettingOptions } from '../selectors/index_setting_options';
import { DefineIndexOptions } from '../selectors/define_index_options';
import { ACCELERATION_TIME_INTERVAL } from '../../../../common/constants';

export interface CreateAccelerationProps {
dataSource: string;
Expand All @@ -37,18 +39,21 @@ export const CreateAcceleration = ({
const [accelerationFormData, setAccelerationFormData] = useState<CreateAccelerationForm>({
dataSource: '',
dataTable: '',
database: '',
dataTableFields: [],
accelerationIndexType: 'skipping',
queryBuilderType: 'visual',
skippingIndexQueryData: [],
coveringIndexQueryData: '',
materializedViewQueryData: '',
coveringIndexQueryData: [],
materializedViewQueryData: {} as materializedViewQueryType,
ps48 marked this conversation as resolved.
Show resolved Hide resolved
accelerationIndexName: '',
accelerationIndexAlias: '',
primaryShardsCount: 5,
replicaShardsCount: 1,
refreshType: 'auto',
refreshIntervalSeconds: undefined,
checkpointLocation: undefined,
refreshIntervalOptions: {
refreshWindow: 1,
refreshInterval: ACCELERATION_TIME_INTERVAL[1].value,
},
});

const copyToEditor = () => {
Expand All @@ -73,7 +78,13 @@ export const CreateAcceleration = ({
accelerationFormData={accelerationFormData}
setAccelerationFormData={setAccelerationFormData}
/>
<IndexTypeSelector
<EuiSpacer size="xxl" />
<IndexSettingOptions
accelerationFormData={accelerationFormData}
setAccelerationFormData={setAccelerationFormData}
/>
<EuiSpacer size="xxl" />
<DefineIndexOptions
accelerationFormData={accelerationFormData}
setAccelerationFormData={setAccelerationFormData}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const CreateAccelerationHeader = () => {
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiTitle size="l" data-test-subj="acceleration-header">
<h1>Create Acceleration Index</h1>
<h1>Accelerate data</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
Expand Down
30 changes: 30 additions & 0 deletions public/components/acceleration/create/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export const isTimePlural = (timeWindow: number) => {
return timeWindow > 1 ? 's' : '';
ps48 marked this conversation as resolved.
Show resolved Hide resolved
};

export const validateIndexName = (value: string) => {
ps48 marked this conversation as resolved.
Show resolved Hide resolved
const lowercaseUnderscoreHyphenRegex = /^[a-z_\-]+$/;

if (!lowercaseUnderscoreHyphenRegex.test(value)) {
return false;
}

// Check if the value does not begin with underscores or hyphens
if (value.startsWith('_') || value.startsWith('-')) {
ps48 marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

// Check if the value does not contain disallowed characters
const disallowedCharacters = /[\s,:"*+\/|?#><]/;
if (disallowedCharacters.test(value)) {
ps48 marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

// If all checks pass, the value is valid
return true;
};
130 changes: 130 additions & 0 deletions public/components/acceleration/selectors/define_index_options.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { ChangeEvent, useEffect, useState } from 'react';
import {
EuiButton,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiIconTip,
EuiLink,
EuiMarkdownFormat,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiSpacer,
EuiText,
EuiHorizontalRule,
ps48 marked this conversation as resolved.
Show resolved Hide resolved
} from '@elastic/eui';
import { CreateAccelerationForm } from '../../../../common/types';
import { validateIndexName } from '../create/utils';
import { ACCELERATION_INDEX_NAME_INFO } from '../../../../common/constants';

interface DefineIndexOptionsProps {
accelerationFormData: CreateAccelerationForm;
setAccelerationFormData: React.Dispatch<React.SetStateAction<CreateAccelerationForm>>;
}

export const DefineIndexOptions = ({
accelerationFormData,
setAccelerationFormData,
}: DefineIndexOptionsProps) => {
const [indexName, setIndexName] = useState('');
const [isModalVisible, setIsModalVisible] = useState(false);

let modal;
ps48 marked this conversation as resolved.
Show resolved Hide resolved

if (isModalVisible) {
modal = (
<EuiModal maxWidth={850} onClose={() => setIsModalVisible(false)}>
<EuiModalHeader>
<EuiModalHeaderTitle>
<h1>Acceleration index naming</h1>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiMarkdownFormat>{ACCELERATION_INDEX_NAME_INFO}</EuiMarkdownFormat>
</EuiFlexItem>
</EuiFlexGroup>
</EuiModalBody>
<EuiModalFooter>
<EuiButton onClick={() => setIsModalVisible(false)} fill>
Close
</EuiButton>
</EuiModalFooter>
</EuiModal>
);
}

const onChangeIndexName = (e: ChangeEvent<HTMLInputElement>) => {
setAccelerationFormData({ ...accelerationFormData, accelerationIndexName: e.target.value });
setIndexName(e.target.value);
};

useEffect(() => {
accelerationFormData.accelerationIndexType === 'skipping'
? setIndexName('skipping')
: setIndexName('');
}, [accelerationFormData.accelerationIndexType]);
ps48 marked this conversation as resolved.
Show resolved Hide resolved

const getPreprend = () => {
const dataSource =
accelerationFormData.dataSource !== ''
? accelerationFormData.dataSource
: '{Datasource Name}';
const database =
accelerationFormData.database !== '' ? accelerationFormData.database : '{Database Name}';
const dataTable =
accelerationFormData.dataTable !== '' ? accelerationFormData.dataTable : '{Table Name}';
const prependValue = `flint_${dataSource}_${database}_${dataTable}_`;
return [
prependValue,
<EuiIconTip type="iInCircle" color="subdued" content={prependValue} position="top" />,
];
};

const getAppend = () => {
const appendValue =
accelerationFormData.accelerationIndexType === 'materialized' ? '' : '_index';
return appendValue;
};

return (
<>
<EuiText data-test-subj="define-index-header">
<h3>Index settings</h3>
</EuiText>
<EuiSpacer size="s" />
<EuiFormRow
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.'
labelAppend={
<EuiText size="xs">
<EuiLink onClick={() => setIsModalVisible(true)}>Help</EuiLink>
</EuiText>
}
>
<EuiFieldText
placeholder="Enter index name"
value={indexName}
onChange={onChangeIndexName}
aria-label="Enter Index Name"
prepend={getPreprend()}
append={getAppend()}
disabled={accelerationFormData.accelerationIndexType === 'skipping'}
isInvalid={validateIndexName(indexName)}
/>
</EuiFormRow>
{modal}
</>
);
};
Loading
Loading