From eb519a67ef686773e289991826385d382e0c88c0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 20 Oct 2023 21:46:29 +0000 Subject: [PATCH] Added changes for making tree view persistent, made changes for bugs for loading screen (#153) * made the tree persistant, added changes for loading screen Signed-off-by: sumukhswamy * changes for the text displayed during loading screen Signed-off-by: sumukhswamy * addressed pr comments Signed-off-by: sumukhswamy * addressed pr comments, added code for reload button Signed-off-by: sumukhswamy * addressed pr comments, added Materialiazed view badges, bug fixes for loading state Signed-off-by: sumukhswamy * updated snapshots Signed-off-by: sumukhswamy * added tests, addressed pr comments Signed-off-by: sumukhswamy * added fix for text overflowing Signed-off-by: sumukhswamy --------- Signed-off-by: sumukhswamy (cherry picked from commit 75f9cca194453cec6b9320a0ef4fbc92c932241a) Signed-off-by: github-actions[bot] --- common/constants/index.ts | 12 +- common/types/index.ts | 10 +- .../Main/__snapshots__/main.test.tsx.snap | 272 ++++- public/components/Main/main.tsx | 35 +- public/components/SQLPage/TableView.tsx | 266 ----- .../__snapshots__/table_view.test.tsx.snap | 1016 +++++++++++++++++ public/components/SQLPage/table_view.test.tsx | 57 + public/components/SQLPage/table_view.tsx | 405 +++++++ test/mocks/mockData.ts | 16 + 9 files changed, 1788 insertions(+), 301 deletions(-) delete mode 100644 public/components/SQLPage/TableView.tsx create mode 100644 public/components/SQLPage/__snapshots__/table_view.test.tsx.snap create mode 100644 public/components/SQLPage/table_view.test.tsx create mode 100644 public/components/SQLPage/table_view.tsx diff --git a/common/constants/index.ts b/common/constants/index.ts index ea25495a..db990084 100644 --- a/common/constants/index.ts +++ b/common/constants/index.ts @@ -10,8 +10,14 @@ export const OPENSEARCH_ACC_DOCUMENTATION_URL = export const ACC_INDEX_TYPE_DOCUMENTATION_URL = 'https://github.com/opensearch-project/opensearch-spark/blob/main/docs/index.md'; -export const SKIPPING_INDEX = `skipping_index`; -export const ON_LOAD_QUERY = `SHOW tables LIKE '%';`; +export const TREE_ITEM_SKIPPING_INDEX_DEFAULT_NAME = `skipping_index`; +export const TREE_ITEM_COVERING_INDEX_DEFAULT_NAME = `covering_index`; +export const TREE_ITEM_MATERIALIZED_VIEW_DEFAULT_NAME = `materialized_view`; +export const TREE_ITEM_DATABASE_NAME_DEFAULT_NAME = `database`; +export const TREE_ITEM_TABLE_NAME_DEFAULT_NAME = `table`; +export const TREE_ITEM_LOAD_MATERIALIZED_BADGE_NAME = `Load Materialized View`; +export const TREE_ITEM_BADGE_NAME =`badge` +export const LOAD_OPENSEARCH_INDICES_QUERY = `SHOW tables LIKE '%';`; export const SKIPPING_INDEX_QUERY = `CREATE SKIPPING INDEX ON myS3.logs_db.http_logs (status VALUE_SET) WITH ( @@ -84,3 +90,5 @@ export const ACCELERATION_INDEX_NAME_INFO = `All OpenSearch acceleration indices `; export const SIDEBAR_POLL_INTERVAL_MS = 5000; + +export const FETCH_OPENSEARCH_INDICES_PATH = '/api/sql_console/sqlquery' \ No newline at end of file diff --git a/common/types/index.ts b/common/types/index.ts index a941b29e..5ed212c8 100644 --- a/common/types/index.ts +++ b/common/types/index.ts @@ -79,4 +79,12 @@ export interface CreateAccelerationForm { formErrors: FormErrorsType; } -export type AsyncQueryLoadingStatus = 'SUCCESS' | 'FAILED' | 'RUNNING' | 'SCHEDULED' | 'CANCELLED'; \ No newline at end of file +export type AsyncQueryLoadingStatus = 'SUCCESS' | 'FAILED' | 'RUNNING' | 'SCHEDULED' | 'CANCELLED'; +export type TreeItemType = 'covering_index' | 'skipping_index' | 'table' | 'database' | 'materialized_view' | 'Load Materialized View' | 'badge' + +export interface TreeItem { + name: string; + type: TreeItemType; + isExpanded: boolean; + values?: TreeItem[]; +} diff --git a/public/components/Main/__snapshots__/main.test.tsx.snap b/public/components/Main/__snapshots__/main.test.tsx.snap index baa291c8..6d65fd66 100644 --- a/public/components/Main/__snapshots__/main.test.tsx.snap +++ b/public/components/Main/__snapshots__/main.test.tsx.snap @@ -193,7 +193,7 @@ exports[`
spec click clear button 1`] = ` >
spec click clear button 1`] = ` >
+ > +
+
+ +
+
+
+
@@ -234,11 +266,11 @@ exports[`
spec click clear button 1`] = `
-

- Error loading Datasources -

+ Error loading data +
@@ -715,7 +747,7 @@ exports[`
spec click run button, and response causes an error 1`] = ` >
spec click run button, and response causes an error 1`] = ` >
+ > +
+
+ +
+
+
+
@@ -756,11 +820,11 @@ exports[`
spec click run button, and response causes an error 1`] = `
-

- Error loading Datasources -

+ Error loading data +
@@ -1237,7 +1301,7 @@ exports[`
spec click run button, and response is not ok 1`] = ` >
spec click run button, and response is not ok 1`] = ` >
+ > +
+
+ +
+
+
+
@@ -1278,11 +1374,11 @@ exports[`
spec click run button, and response is not ok 1`] = `
-

- Error loading Datasources -

+ Error loading data +
@@ -1759,7 +1855,7 @@ exports[`
spec click run button, and response is ok 1`] = ` >
spec click run button, and response is ok 1`] = ` >
+ > +
+
+ +
+
+
+
@@ -2369,7 +2497,7 @@ exports[`
spec click run button, response fills null and missing values >
spec click run button, response fills null and missing values >
+ > +
+
+ +
+
+
+
@@ -2978,7 +3138,7 @@ exports[`
spec click translation button, and response is ok 1`] = ` >
spec click translation button, and response is ok 1`] = ` >
+ > +
+
+ +
+
+
+
@@ -3019,11 +3211,11 @@ exports[`
spec click translation button, and response is ok 1`] = `
-

- Error loading Datasources -

+ Error loading data +
@@ -3491,7 +3683,7 @@ exports[`
spec renders the component 1`] = ` >
spec renders the component 1`] = ` >
+ > +
+
+ +
+
+
+
diff --git a/public/components/Main/main.tsx b/public/components/Main/main.tsx index 7e8382d8..9cf36c94 100644 --- a/public/components/Main/main.tsx +++ b/public/components/Main/main.tsx @@ -5,6 +5,7 @@ import { EuiButton, + EuiButtonIcon, EuiComboBoxOptionOption, EuiFlexGroup, EuiFlexItem, @@ -36,7 +37,7 @@ import QueryResults from '../QueryResults/QueryResults'; import { CreateButton } from '../SQLPage/CreateButton'; import { DataSelect } from '../SQLPage/DataSelect'; import { SQLPage } from '../SQLPage/SQLPage'; -import { TableView } from '../SQLPage/TableView'; +import { TableView } from '../SQLPage/table_view'; interface ResponseData { ok: boolean; @@ -111,6 +112,7 @@ interface MainState { asyncLoadingStatus: AsyncQueryLoadingStatus; asyncQueryError: string; asyncJobId: string; + refreshTree: boolean; isAccelerationFlyoutOpened: boolean; isCallOutVisible: boolean; } @@ -251,6 +253,7 @@ export class Main extends React.Component { asyncLoadingStatus: 'SUCCESS', asyncQueryError: '', asyncJobId: '', + refreshTree: false, isAccelerationFlyoutOpened: false, isCallOutVisible: false, }; @@ -814,6 +817,12 @@ export class Main extends React.Component { }); }; + handleReloadTree = () => { + this.setState({ + refreshTree: !this.state.refreshTree, + }); + }; + setIsAccelerationFlyoutOpened = (value: boolean) => { this.setState({ isAccelerationFlyoutOpened: value, @@ -942,20 +951,34 @@ export class Main extends React.Component { {this.state.language === 'SQL' && ( - + - + + + + + + + + diff --git a/public/components/SQLPage/TableView.tsx b/public/components/SQLPage/TableView.tsx deleted file mode 100644 index 017fbf19..00000000 --- a/public/components/SQLPage/TableView.tsx +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - EuiComboBoxOptionOption, - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiLoadingSpinner, - EuiText, - EuiToolTip, - EuiTreeView, -} from '@elastic/eui'; -import _ from 'lodash'; -import React, { useEffect, useState } from 'react'; -import { CoreStart } from '../../../../../src/core/public'; -import { ON_LOAD_QUERY, SKIPPING_INDEX } from '../../../common/constants'; -import { AccelerationIndexFlyout } from './acceleration_index_flyout'; -import { getJobId, pollQueryStatus } from './utils'; - -interface CustomView { - http: CoreStart['http']; - selectedItems: EuiComboBoxOptionOption[]; - updateSQLQueries: (query: string) => void; -} - -export const TableView = ({ http, selectedItems, updateSQLQueries }: CustomView) => { - const [tablenames, setTablenames] = useState([]); - const [selectedNode, setSelectedNode] = useState(null); - const [childData, setChildData] = useState([]); - const [selectedChildNode, setSelectedChildNode] = useState(null); - const [indexData, setIndexedData] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [indexFlyout, setIndexFlyout] = useState(<>); - const [childLoadingStates, setChildLoadingStates] = useState<{ [key: string]: boolean }>({}); - const [tableLoadingStates, setTableLoadingStates] = useState<{ [key: string]: boolean }>({}); - - let indiciesData: string[] = []; - - const resetFlyout = () => { - setIndexFlyout(<>); - }; - - const handleAccelerationIndexClick = ( - dataSource: string, - database: string, - dataTable: string, - indexName: string - ) => { - setIndexFlyout( - - ); - }; - - const get_async_query_results = (id, http, callback) => { - pollQueryStatus(id, http, callback); - }; - - const getSidebarContent = () => { - if (selectedItems[0].label === 'OpenSearch') { - setTablenames([]); - const query = { query: ON_LOAD_QUERY }; - http - .post(`/api/sql_console/sqlquery`, { - body: JSON.stringify(query), - }) - .then((res) => { - const responseObj = res.data.resp ? JSON.parse(res.data.resp) : ''; - const datarows: any[][] = _.get(responseObj, 'datarows'); - const fields = datarows.map((data) => { - return data[2]; - }); - setTablenames(fields); - }) - .catch((err) => { - console.error(err); - }); - } else { - setIsLoading(true); - setTablenames([]); - const query = { - lang: 'sql', - query: `SHOW SCHEMAS IN ${selectedItems[0]['label']}`, - datasource: selectedItems[0]['label'], - }; - getJobId(query, http, (id) => { - get_async_query_results(id, http, (data) => { - setTablenames(data); - setIsLoading(false); - }); - }); - } - }; - - useEffect(() => { - setIsLoading(false); - getSidebarContent(); - }, [selectedItems]); - - const handleNodeClick = (nodeLabel: string) => { - setSelectedNode(nodeLabel); - const query = { - lang: 'sql', - query: `SHOW TABLES IN ${selectedItems[0]['label']}.${nodeLabel}`, - datasource: selectedItems[0]['label'], - }; - setTableLoadingStates((prevState) => ({ - ...prevState, - [nodeLabel]: true, - })); - getJobId(query, http, (id) => { - get_async_query_results(id, http, (data) => { - data = data.map((subArray) => subArray[1]); - setChildData(data); - - setTableLoadingStates((prevState) => ({ - ...prevState, - [nodeLabel]: false, - })); - }); - }); - }; - - const callCoverQuery = (nodeLabel1: string) => { - const coverQuery = { - lang: 'sql', - query: `SHOW INDEX ON ${selectedItems[0]['label']}.${selectedNode}.${nodeLabel1}`, - datasource: selectedItems[0]['label'], - }; - getJobId(coverQuery, http, (id) => { - get_async_query_results(id, http, (data) => { - const res = [].concat(data); - const final = indiciesData.concat(...res); - setIndexedData(final); - setChildLoadingStates((prevState) => ({ - ...prevState, - [nodeLabel1]: false, - })); - }); - }); - }; - const handleChildClick = (nodeLabel1: string) => { - setSelectedChildNode(nodeLabel1); - const skipQuery = { - lang: 'sql', - query: `DESC SKIPPING INDEX ON ${selectedItems[0]['label']}.${selectedNode}.${nodeLabel1}`, - datasource: selectedItems[0]['label'], - }; - setChildLoadingStates((prevState) => ({ - ...prevState, - [nodeLabel1]: true, - })); - - getJobId(skipQuery, http, (id) => { - get_async_query_results(id, http, (data) => { - if (data.length > 0) { - indiciesData.push(SKIPPING_INDEX); - } - callCoverQuery(nodeLabel1); - }); - }); - }; - - const treeData = tablenames.map((database, index) => ({ - label: ( -
- - {_.truncate(database, { length: 50 })} - {' '} - {tableLoadingStates[database] && } -
- ), - icon: , - id: 'element_' + index, - callback: () => { - setChildData([]); - selectedItems[0].label !== 'OpenSearch' && handleNodeClick(database); - }, - isSelectable: true, - isExpanded: true, - children: - selectedNode === database - ? childData.map((table) => ({ - label: ( -
- - {_.truncate(table, { length: 50 })} - {' '} - {childLoadingStates[table] && } -
- ), - id: `${database}_${table}`, - icon: , - callback: () => { - setIndexedData([]); - handleChildClick(table); - setChildLoadingStates((prevState) => ({ - ...prevState, - [selectedChildNode]: false, - })); - }, - sSelectable: true, - isExpanded: true, - children: - selectedChildNode === table - ? indexData.map((indexChild) => ({ - label: ( -
- - {_.truncate(indexChild, { length: 50 })} - -
- ), - id: `${table}_${indexChild}`, - icon: , - callback: () => - handleAccelerationIndexClick( - selectedItems[0].label, - database, - table, - indexChild - ), - })) - : undefined, - })) - : undefined, - })); - - return ( - <> - - {isLoading ? ( - - Loading databases - - - - - ) : treeData.length > 0 ? ( - - - - ) : ( - - Error loading Datasources} - /> - - )} - {indexFlyout} - - - ); -}; diff --git a/public/components/SQLPage/__snapshots__/table_view.test.tsx.snap b/public/components/SQLPage/__snapshots__/table_view.test.tsx.snap new file mode 100644 index 00000000..6b8be8c2 --- /dev/null +++ b/public/components/SQLPage/__snapshots__/table_view.test.tsx.snap @@ -0,0 +1,1016 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Render databases in tree fetches and displays database nodes when datasource is s3 1`] = ` +
+
+
+
+ +
+

+ Error loading data +

+
+
+
+
+`; + +exports[`Render databases in tree fetches and displays indicies when datasource is OpenSearch 1`] = ` +
+
+
+
+

+ You can quickly navigate this list using arrow keys. +

+
    +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
  • + +
    +
  • +
+
+
+
+
+`; diff --git a/public/components/SQLPage/table_view.test.tsx b/public/components/SQLPage/table_view.test.tsx new file mode 100644 index 00000000..3f602918 --- /dev/null +++ b/public/components/SQLPage/table_view.test.tsx @@ -0,0 +1,57 @@ +import '@testing-library/jest-dom'; +import React from 'react'; +import { httpClientMock } from '../../../test/mocks'; + +import '@testing-library/jest-dom/extend-expect'; +import { render, waitFor } from '@testing-library/react'; +import { HttpResponse } from '../../../../../src/core/public'; +import { mockDatabaseQuery, mockJobId, mockOpenSearchIndicies } from '../../../test/mocks/mockData'; +import { TableView } from './table_view'; + +describe('Render databases in tree', () => { + it('fetches and displays indicies when datasource is OpenSearch', async () => { + const client = httpClientMock; + client.post = jest.fn(() => { + return (Promise.resolve(mockOpenSearchIndicies) as unknown) as HttpResponse; + }); + + const asyncTest = () => { + render( + {}} + refreshTree={false} + /> + ); + }; + await asyncTest(); + expect(document.body.children[0]).toMatchSnapshot(); + }); + it('fetches and displays database nodes when datasource is s3', async () => { + const client = httpClientMock; + client.post = jest.fn(() => { + return (Promise.resolve(mockJobId) as unknown) as HttpResponse; + }); + client.get = jest.fn(() => { + return (Promise.resolve(mockDatabaseQuery) as unknown) as HttpResponse; + }); + + const { getByText } = render( + {}} + refreshTree={false} + /> + ); + await waitFor(() => { + expect( + getByText( + 'Loading can take more than 30s. Queries can be made after the data has loaded. Any queries run before the data is loaded will be queued' + ) + ).toBeInTheDocument(); + }); + expect(document.body.children[0]).toMatchSnapshot(); + }); +}); diff --git a/public/components/SQLPage/table_view.tsx b/public/components/SQLPage/table_view.tsx new file mode 100644 index 00000000..eb2a44bf --- /dev/null +++ b/public/components/SQLPage/table_view.tsx @@ -0,0 +1,405 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiBadge, + EuiComboBoxOptionOption, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLoadingSpinner, + EuiText, + EuiToolTip, + EuiTreeView, +} from '@elastic/eui'; +import { TreeItem, TreeItemType } from 'common/types'; +import _ from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { CoreStart } from '../../../../../src/core/public'; +import { + FETCH_OPENSEARCH_INDICES_PATH, + LOAD_OPENSEARCH_INDICES_QUERY, + TREE_ITEM_BADGE_NAME, + TREE_ITEM_COVERING_INDEX_DEFAULT_NAME, + TREE_ITEM_DATABASE_NAME_DEFAULT_NAME, + TREE_ITEM_LOAD_MATERIALIZED_BADGE_NAME, + TREE_ITEM_MATERIALIZED_VIEW_DEFAULT_NAME, + TREE_ITEM_SKIPPING_INDEX_DEFAULT_NAME, + TREE_ITEM_TABLE_NAME_DEFAULT_NAME, +} from '../../../common/constants'; +import { AccelerationIndexFlyout } from './acceleration_index_flyout'; +import { getJobId, pollQueryStatus } from './utils'; + +interface CustomView { + http: CoreStart['http']; + selectedItems: EuiComboBoxOptionOption[]; + updateSQLQueries: (query: string) => void; + refreshTree: boolean; +} + +export const TableView = ({ http, selectedItems, updateSQLQueries, refreshTree }: CustomView) => { + const [tableNames, setTableNames] = useState([]); + const [selectedDatabase, setSelectedDatabase] = useState(''); + const [selectedTable, setSelectedTable] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [indexFlyout, setIndexFlyout] = useState(<>); + const [treeData, setTreeData] = useState([]); + + const resetFlyout = () => { + setIndexFlyout(<>); + }; + + const handleAccelerationIndexClick = ( + dataSource: string, + database: string, + dataTable: string, + indexName: string + ) => { + setIndexFlyout( + + ); + }; + + function loadTreeItem(elements: string[], type: TreeItemType): TreeItem[] { + return elements.map((element) => { + let treeItem: TreeItem = { + name: element, + type: type, + isExpanded: true, + }; + + if ( + type != TREE_ITEM_COVERING_INDEX_DEFAULT_NAME && + type != TREE_ITEM_SKIPPING_INDEX_DEFAULT_NAME + ) { + treeItem.values = []; + } + return treeItem; + }); + } + + const get_async_query_results = (id, http, callback) => { + pollQueryStatus(id, http, callback); + }; + + const getSidebarContent = () => { + if (selectedItems[0].label === 'OpenSearch') { + setTableNames([]); + const query = { query: LOAD_OPENSEARCH_INDICES_QUERY }; + http + .post(FETCH_OPENSEARCH_INDICES_PATH, { + body: JSON.stringify(query), + }) + .then((res) => { + const responseObj = res.data.resp ? JSON.parse(res.data.resp) : {}; + const dataRows: any[][] = _.get(responseObj, 'datarows'); + const fields = dataRows.map((data) => { + return data[2]; + }); + setTableNames(fields); + }) + .catch((err) => { + console.error(err); + }); + } else { + setIsLoading(true); + setTableNames([]); + const query = { + lang: 'sql', + query: `SHOW SCHEMAS IN ${selectedItems[0]['label']}`, + datasource: selectedItems[0]['label'], + }; + getJobId(query, http, (id) => { + get_async_query_results(id, http, (data) => { + data = [].concat(...data); + setTreeData(loadTreeItem(data, TREE_ITEM_DATABASE_NAME_DEFAULT_NAME)); + setIsLoading(false); + }); + }); + } + }; + + useEffect(() => { + setIsLoading(false); + getSidebarContent(); + }, [selectedItems, refreshTree]); + + const handleDatabaseClick = (databaseName: string) => { + setSelectedDatabase(databaseName); + setIsLoading(true); + const query = { + lang: 'sql', + query: `SHOW TABLES IN ${selectedItems[0]['label']}.${databaseName}`, + datasource: selectedItems[0]['label'], + }; + getJobId(query, http, (id) => { + get_async_query_results(id, http, (data) => { + data = data.map((subArray) => subArray[1]); + let values = loadTreeItem(data, TREE_ITEM_TABLE_NAME_DEFAULT_NAME); + let mvObj = loadTreeItem( + [TREE_ITEM_LOAD_MATERIALIZED_BADGE_NAME], + TREE_ITEM_LOAD_MATERIALIZED_BADGE_NAME + ); + values = [...values, ...mvObj]; + setTreeData((prevTreeData) => { + return prevTreeData.map((database) => { + if (database.name === databaseName) { + return { ...database, values: values }; + } + return database; + }); + }); + setIsLoading(false); + }); + }); + }; + + const loadCoveringIndex = (tableName: string) => { + const coverQuery = { + lang: 'sql', + query: `SHOW INDEX ON ${selectedItems[0]['label']}.${selectedDatabase}.${tableName}`, + datasource: selectedItems[0]['label'], + }; + getJobId(coverQuery, http, (id) => { + get_async_query_results(id, http, (data) => { + const res = [].concat(data); + let coverIndexObj = loadTreeItem(res, TREE_ITEM_COVERING_INDEX_DEFAULT_NAME); + setTreeData((prevTreeData) => { + return prevTreeData.map((database) => { + if (database.name === selectedDatabase) { + return { + ...database, + values: database.values?.map((table) => { + if (table.name === tableName) { + return { + ...table, + values: table.values?.concat(...coverIndexObj), + }; + } + return table; + }), + }; + } + return database; + }); + }); + setIsLoading(false); + }); + }); + }; + + const handleButtonClick = (e: MouseEvent, tableName: string) => { + e.stopPropagation(); + setSelectedTable(tableName); + setIsLoading(true); + const materializedViewQuery = { + lang: 'sql', + query: `SHOW MATERIALIZED VIEW IN ${selectedItems[0]['label']}.${selectedDatabase}`, + datasource: selectedItems[0]['label'], + }; + getJobId(materializedViewQuery, http, (id) => { + get_async_query_results(id, http, (data) => { + data = data.map((subArray) => subArray[1]); + let values = loadTreeItem(data, TREE_ITEM_MATERIALIZED_VIEW_DEFAULT_NAME); + if (values.length === 0) { + values = [ + { name: 'No Materialized View', type: TREE_ITEM_BADGE_NAME, isExpanded: false }, + ]; + } + setTreeData((prevTreeData) => { + return prevTreeData.map((database) => { + if (database.name === selectedDatabase) { + const updatedValues = database.values?.filter( + (item) => item.type !== TREE_ITEM_LOAD_MATERIALIZED_BADGE_NAME + ); + return { ...database, values: updatedValues?.concat(...values) }; + } + return database; + }); + }); + setIsLoading(false); + }); + }); + }; + + const handleTableClick = (tableName: string) => { + setSelectedTable(tableName); + setIsLoading(true); + const skipQuery = { + lang: 'sql', + query: `DESC SKIPPING INDEX ON ${selectedItems[0]['label']}.${selectedDatabase}.${tableName}`, + datasource: selectedItems[0]['label'], + }; + getJobId(skipQuery, http, (id) => { + get_async_query_results(id, http, (data) => { + if (data.length > 0) { + setTreeData((prevTreeData) => { + return prevTreeData.map((database) => { + if (database.name === selectedDatabase) { + return { + ...database, + values: database.values?.map((table) => { + if (table.name === tableName) { + return { + ...table, + values: loadTreeItem( + [TREE_ITEM_SKIPPING_INDEX_DEFAULT_NAME], + TREE_ITEM_SKIPPING_INDEX_DEFAULT_NAME + ), + }; + } + return table; + }), + }; + } + return database; + }); + }); + } + loadCoveringIndex(tableName); + }); + }); + }; + + const createLabel = (node: TreeItem, parentName: string, index: number) => { + switch (node.type) { + case TREE_ITEM_BADGE_NAME: + return ( +
+ + {_.truncate(node.name, { length: 50 })} + {' '} +
+ ); + + case TREE_ITEM_LOAD_MATERIALIZED_BADGE_NAME: + return ( +
+ + Load Materialized View + +
+ ); + + default: + return ( +
+ + {_.truncate(node.name, { length: 50 })} + {' '} +
+ ); + } + }; + + const OpenSearchIndicesTree = tableNames.map((database, index) => ({ + label: ( +
+ + {_.truncate(database, { length: 50 })} + {' '} +
+ ), + icon: , + id: 'element_' + index, + isSelectable: false, + })); + + const treeDataDatabases = treeData.map((database, index) => ({ + label: createLabel(database, selectedItems[0].label, index), + icon: , + id: 'element_' + index, + callback: () => { + if (database.values?.length === 0 && selectedItems[0].label !== 'OpenSearch') { + handleDatabaseClick(database.name); + } + }, + isSelectable: true, + isExpanded: database.isExpanded, + children: database.values?.map((table, index) => ({ + label: createLabel(table, database.name, index), + id: `${database.name}_${table.name}`, + icon: + table.type === TREE_ITEM_LOAD_MATERIALIZED_BADGE_NAME ? ( + MV + ) : table.type === TREE_ITEM_BADGE_NAME ? null : ( + + ), + callback: () => { + if (table.type !== TREE_ITEM_LOAD_MATERIALIZED_BADGE_NAME && table.values?.length === 0) { + handleTableClick(table.name); + } + if (table.values?.length === 0) { + table.values = [{ name: 'No Indicies', type: TREE_ITEM_BADGE_NAME, isExpanded: false }]; + } + }, + isSelectable: true, + isExpanded: table.isExpanded, + children: table.values?.map((indexChild, index) => ({ + label: createLabel(indexChild, table.name, index), + id: `${database.name}_${table.name}_${indexChild.name}`, + icon: indexChild.type === TREE_ITEM_BADGE_NAME ? null : , + callback: () => { + if (indexChild.type !== TREE_ITEM_BADGE_NAME) { + handleAccelerationIndexClick( + selectedItems[0].label, + database.name, + table.name, + indexChild.name + ); + } + }, + })), + })), + })); + + return ( + <> + + {isLoading ? ( + + + + + Loading data + +
+ + Loading can take more than 30s. Queries can be made after the data has loaded. Any + queries run before the data is loaded will be queued + +
+
+
+ ) : OpenSearchIndicesTree.length > 0 || treeDataDatabases.length > 0 ? ( + + {selectedItems[0].label === 'OpenSearch' ? ( + + ) : ( + + )} + + ) : ( + + Error loading data} + /> + + )} + {indexFlyout} +
+ + ); +}; diff --git a/test/mocks/mockData.ts b/test/mocks/mockData.ts index 65904eec..fee838f0 100644 --- a/test/mocks/mockData.ts +++ b/test/mocks/mockData.ts @@ -2349,4 +2349,20 @@ export const mockDatasourcesQuery = } } +export const mockJobId = { + data: { + ok: true, + resp: '{ "queryId": "00fe3fjpnfnn400q" }', + }, +}; + +export const mockOpenSearchIndicies = { + data: { + ok: true, + resp: + '{"schema":[{"name":"TABLE_CAT","type":"keyword"},{"name":"TABLE_SCHEM","type":"keyword"},{"name":"TABLE_NAME","type":"keyword"},{"name":"TABLE_TYPE","type":"keyword"},{"name":"REMARKS","type":"keyword"},{"name":"TYPE_CAT","type":"keyword"},{"name":"TYPE_SCHEM","type":"keyword"},{"name":"TYPE_NAME","type":"keyword"},{"name":"SELF_REFERENCING_COL_NAME","type":"keyword"},{"name":"REF_GENERATION","type":"keyword"}],"datarows":[["opensearch",null,".kibana_1","BASE TABLE",null,null,null,null,null,null],["opensearch",null,".kibana_2","BASE TABLE",null,null,null,null,null,null],["opensearch",null,".kibana_3","BASE TABLE",null,null,null,null,null,null],["opensearch",null,".opendistro-reports-definitions","BASE TABLE",null,null,null,null,null,null],["opensearch",null,".opendistro-reports-instances","BASE TABLE",null,null,null,null,null,null],["opensearch",null,".opensearch-observability","BASE TABLE",null,null,null,null,null,null],["opensearch",null,".plugins-ml-config","BASE TABLE",null,null,null,null,null,null],["opensearch",null,".ql-datasources","BASE TABLE",null,null,null,null,null,null],["opensearch",null,".ql-job-metadata","BASE TABLE",null,null,null,null,null,null],["opensearch",null,".query_execution_result","BASE TABLE",null,null,null,null,null,null],["opensearch",null,"flint_mys3_default_alb_logs_temp_10_test_index","BASE TABLE",null,null,null,null,null,null],["opensearch",null,"flint_mys3_default_alb_logs_temp_5_mys3_default_alb_logs_temp_5_index","BASE TABLE",null,null,null,null,null,null],["opensearch",null,"flint_mys3_default_elb_logs_regex_elb_logs_regex_index_index","BASE TABLE",null,null,null,null,null,null],["opensearch",null,"flint_mys3_default_empty_table_empty_table_index_index","BASE TABLE",null,null,null,null,null,null],["opensearch",null,"flint_mys3_default_http_logs_skipping_index","BASE TABLE",null,null,null,null,null,null],["opensearch",null,"flint_mys3_default_parquet_elb_logs_simple_parquet_index_simple_index","BASE TABLE",null,null,null,null,null,null],["opensearch",null,"opensearch_dashboards_sample_data_flights","BASE TABLE",null,null,null,null,null,null],["opensearch",null,"opensearch_dashboards_sample_data_logs","BASE TABLE",null,null,null,null,null,null],["opensearch",null,"ss4o_traces-elb-test","BASE TABLE",null,null,null,null,null,null],["opensearch",null,".flint-elv-mv","BASE TABLE",null,null,null,null,null,null],["opensearch",null,".kibana","BASE TABLE",null,null,null,null,null,null]],"total":21,"size":21,"status":200}', + }, +}; + +