diff --git a/common/types/data_connections.ts b/common/types/data_connections.ts index 319ff0213..8ebab0d11 100644 --- a/common/types/data_connections.ts +++ b/common/types/data_connections.ts @@ -39,15 +39,31 @@ export interface AssociatedObject { name: string; database: string; type: string; - createdByIntegration: string; accelerations: Acceleration[]; - columns: TableColumn[]; + columns?: TableColumn[]; } export type Role = EuiComboBoxOptionOption; export type DatasourceType = 'S3GLUE' | 'PROMETHEUS'; +export interface S3GlueProperties { + 'glue.indexstore.opensearch.uri': string; + 'glue.indexstore.opensearch.region': string; +} + +export interface PrometheusProperties { + 'prometheus.uri': string; +} + +export interface DatasourceDetails { + allowedRoles: string[]; + name: string; + connector: DatasourceType; + description: string; + properties: S3GlueProperties | PrometheusProperties; +} + interface AsyncApiDataResponse { status: string; schema?: Array<{ name: string; type: string }>; @@ -105,7 +121,7 @@ export interface DataSourceCacheData { dataSources: CachedDataSource[]; } -export interface CachedAccelerations { +export interface CachedAcceleration { flintIndexName: string; type: AccelerationIndexType; database: string; @@ -115,16 +131,16 @@ export interface CachedAccelerations { status: string; } -export interface CachedAcclerationByDataSource { +export interface CachedAccelerationByDataSource { name: string; - accelerations: CachedAccelerations[]; + accelerations: CachedAcceleration[]; lastUpdated: string; // date string in UTC format status: CachedDataSourceStatus; } export interface AccelerationsCacheData { version: string; - dataSources: CachedAcclerationByDataSource[]; + dataSources: CachedAccelerationByDataSource[]; } export interface PollingSuccessResult { diff --git a/common/types/explorer.ts b/common/types/explorer.ts index 9c8b20cd2..6a495218c 100644 --- a/common/types/explorer.ts +++ b/common/types/explorer.ts @@ -430,6 +430,7 @@ export enum DirectQueryLoadingStatus { SCHEDULED = 'scheduled', CANCELED = 'canceled', WAITING = 'waiting', + INITIAL = 'initial', } export interface DirectQueryRequest { diff --git a/public/components/datasources/components/__tests__/__snapshots__/associated_objects_tab.test.tsx.snap b/public/components/datasources/components/__tests__/__snapshots__/associated_objects_tab.test.tsx.snap index c687978d5..392c0728a 100644 --- a/public/components/datasources/components/__tests__/__snapshots__/associated_objects_tab.test.tsx.snap +++ b/public/components/datasources/components/__tests__/__snapshots__/associated_objects_tab.test.tsx.snap @@ -2,224 +2,118 @@ exports[`AssociatedObjectsTab Component renders correctly with associated objects 1`] = ` + +
+ + + +
+
+ + + Accelerations recommended for tables. Setup acceleration or configure integrations + +
+
+
+
- -
+ - -
- - - - - -
-
- -
+ +
+ +
+ + +
+ - -
- -
- -
- Last updated at: -
-
-
-
-
-
- + +
+ +
+ + +
+ + +
+ + + +
-
-
- + + + + + + Refresh + + + + + + +
@@ -431,535 +319,89 @@ exports[`AssociatedObjectsTab Component renders correctly with associated object className="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginLarge" /> - - -
-
- - - Accelerations recommended for tables. Setup acceleration or configure integrations - -
-
-
-
- -
- + - -
- -
- - + @@ -1038,40 +486,356 @@ exports[`AssociatedObjectsTab Component renders correctly with associated object
- -
-
- -
+ + - +
+ +
+ +
+ + +
+ +
+
+ +
+ + +
+ - +
- - - Database - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - id="field_value_selection_0" - isOpen={false} - ownFocus={true} - panelClassName="euiFilterGroup__popoverPanel" - panelPaddingSize="none" +
-
-
- - - - - -
-
- - - +
+ +
+
+ + + +
+
+ - - Accelerations - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - id="field_value_selection_1" - isOpen={false} - ownFocus={true} - panelClassName="euiFilterGroup__popoverPanel" - panelPaddingSize="none" +
-
-
- +
- - - - -
-
- - + + + + + + + + + + + + Accelerations + + + + + + + +
+
+
+ +
+ + +
+ - - - - - - - - -
- - -
-
- -
- + + +
+ +
- -
- - -
- + +
-
- - - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="none" +
-
+
+ +
- - + +
+
+ +
+ +
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - + + + - - - + + + + + - - + - - + - - Type - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - + Name + +
- - - + mock_table_1 + + +
+ + + +
+ + + + + - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - + Database + +
- - - - Discover + mock_database_1 - - - - - + + + - - - + Type +
+
- - - - Accelerate + table - - - - - -
- -
- - - - - - - - - - - - - - - - - - - - - + + - + + + + + + + + + - - - - - - + mock_table_3 + + + + + + + + + + + + - + + + - - - - - - - Accelerate - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - + + + + + + + + - + + + - - - - - - - Discover - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - + + + - - - - - -
- -
- - - Name - - - - + + + + + Type + + + + + + + - + - - Database - - - - - - - -
- + + + + + - - Created by Integration - - - - - - - - - - - - - + Type + +
+ + Skipping Index + +
+ + + - - Actions - -
-
-
-
-
-
- Name -
-
- +
+ Accelerations +
+
+ - +
+
+
+ + + + + + + + + + Discover + + + + + + +
+
-
- Database -
-
- - db1 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- - - -
-
-
- Accelerations -
-
- - - -
-
-
- - +
+
+ Name +
+
+ + + +
+
+
+ Database +
+
+ + mock_database_1 + +
+
+
+ Type +
+
+ + Materialized View + +
+
+
+ Accelerations +
+
+ - +
+
+
+ + + + + + + + + + Discover + + + + + + +
+
+
+ Database +
+
- Discover + mock_database_1 - - - - - +
+
+ Type +
+
+ + table + +
+
+
+ - - - - - + + + + + Discover + + + + + + +
+
+
+ Name +
+
+ - Accelerate - - - - - - -
-
-
- Name -
-
- - - -
-
-
- Database -
-
- - db1 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- - - -
-
-
- Accelerations -
-
- - - -
-
-
- - - - - - - - - - Discover - - - - - - - - - - - - - - Accelerate - - - - - - -
-
-
- Name -
-
- - - -
-
-
- Database -
-
- - db2 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- - - -
-
-
- Accelerations -
-
- - - -
-
-
- - - - - - - - - - Discover - - - - - - - - - - - - - - Accelerate - - - - - - -
-
-
- Name -
-
- - - -
-
-
- Database -
-
- - db3 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- - - -
-
-
- Accelerations -
-
- - -
-
-
- + mock_table_2 + + +
+
-
- Name -
-
- - - -
-
-
- Database -
-
- - db3 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- - -
-
-
- Accelerations -
-
- - - - , - - - -
-
-
- +
+
+ Accelerations +
+
+ - +
+
+
+ + + + + + + + + + Discover + + + + + + +
+
+
+ Database +
+
- Discover + mock_database_1 - - - - - +
+
+ Type +
+
+ + table + +
+
+
+ Accelerations +
+
+ - +
+
-
- Name -
-
- - - -
-
-
- Database -
-
- - db3 - -
-
-
- Type -
-
- - Cover Index - -
-
-
- Created by Integration -
-
- - -
-
-
- Accelerations -
-
- + + + + + Discover + + + + + + +
+
-
- - +
+
+ Name +
+
+ + + +
+
+
+ Database +
+
+ + mock_database_1 + +
+
+
+ Type +
+
+ + table + +
+
+
+ Accelerations +
+
+ - +
+
-
- Name -
-
- - - -
-
-
- Database -
-
- - db1 - -
-
-
- Type -
-
- - Skip Index - -
-
-
- Created by Integration -
-
- - - -
-
-
- Accelerations -
-
- + + + + + Discover + + + + + + +
+
-
- - - - - - - - + mock_table_5 + + +
+
+
+ Database +
+
- Discover + mock_database_1 - - - - - -
-
-
- - -
- -
- - - -
- -
- - - : - 10 - - } - closePopover={[Function]} - display="inlineBlock" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="none" - > -
-
- -
+
- - - - - - - +
+ + + +
+
- +
+ - +
+
+
+ - Rows per page - - : - 10 - - - - - -
- - - - - + + + + + + + + Discover + + + + + + + +
+
+
+ -
- + +
+ + -
+ +
+
+ - - + + + +
    + +
  • + + + + + + + + + +
  • +
    +
+ - - - - - - - - - - - + + + + + + + +
+
+
+ +
- +
- - -
- + +
+ +
- + - + @@ -6220,10 +4424,119 @@ exports[`AssociatedObjectsTab Component renders correctly with associated object
`; -exports[`AssociatedObjectsTab Component renders without crashing with no associated objects 1`] = ` +exports[`AssociatedObjectsTab Component renders tab with no databases or objects 1`] = ` + +
+ + + +
+
+ + + Accelerations recommended for tables. Setup acceleration or configure integrations + +
+
+
+
- -
+ - -
- - - - - -
-
- -
+ +
+ +
+ + +
+ - -
- -
- -
- Last updated at: -
-
-
-
-
-
- + +
+ +
+ + +
+ + +
+ + + +
-
-
- + + + + + + Refresh + + + + + + +
@@ -6435,213 +4742,92 @@ exports[`AssociatedObjectsTab Component renders without crashing with no associa className="euiHorizontalRule euiHorizontalRule--full euiHorizontalRule--marginLarge" /> - - -
-
- - - Accelerations recommended for tables. Setup acceleration or configure integrations - -
-
-
-
- -
- - - Query Workbench - - } - body={ -

- Add or config tables from your data source or use Query Workbench. -

- } - title={ -

- You have no associated objects -

- } + -
- -

- You have no associated objects -

-
- - - -
- + -
-

- Add or config tables from your data source or use Query Workbench. -

-
+

+ You have no databases in your data source +

+

+ Add databases and tables to your data source or use Query Workbench +

- - - -
- - - + Learn more + + + } + button={ + - - - -
-
+ + +
+ +
+
+
+
+
diff --git a/public/components/datasources/components/__tests__/__snapshots__/connection_details.test.tsx.snap b/public/components/datasources/components/__tests__/__snapshots__/connection_details.test.tsx.snap index 5b35615a5..dcacaf5d9 100644 --- a/public/components/datasources/components/__tests__/__snapshots__/connection_details.test.tsx.snap +++ b/public/components/datasources/components/__tests__/__snapshots__/connection_details.test.tsx.snap @@ -266,7 +266,7 @@ exports[`Connection Details test Renders connection details for prometheus datas exports[`Connection Details test Renders connection details for s3 datasource 1`] = ` - ya + mock_data_source diff --git a/public/components/datasources/components/__tests__/__snapshots__/data_connection.test.tsx.snap b/public/components/datasources/components/__tests__/__snapshots__/data_connection.test.tsx.snap index 76fc411ae..3dd9bf6e5 100644 --- a/public/components/datasources/components/__tests__/__snapshots__/data_connection.test.tsx.snap +++ b/public/components/datasources/components/__tests__/__snapshots__/data_connection.test.tsx.snap @@ -1,2028 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Data Connection Page test Renders Prometheus data connection page with data 1`] = ` -
-
-
-
-
-
-
-

-

-
-
-
-
-
-
-
-
-
- Connection title -
-
- prom -
-
-
-
- Data source description -
-
- - -
-
-
-
-
-
-
-
- Prometheus URI -
-
- localhost:9201 -
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- -
-
- - - -
-

- Query your data in Metrics Analytics. -

-
-
-
-
-
-
-
-
-
-
-
- - - - -
-
-
-
-
-
-
-

- Associated objects -

- Manage objects associated with this data sources. -
-
-
-
-
- -
-
-
-
-
- Last updated at: -
-
-
-
-
-
- Mon, 01 Jan 2024 00:00:00 GMT -
-
-
-
-
-
-
-
-
-
- - - Accelerations recommended for tables. Setup acceleration or configure integrations - -
-
-
-
-
-
-
-
- -
- - - -
-
-
-
-
-
-
-
- -
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - Actions - - -
-
- Name -
-
- -
-
-
- Database -
-
- - db1 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- -
-
-
- Accelerations -
-
- - -
-
-
- - - - Discover - - - - - - Accelerate - - -
-
-
- Name -
-
- -
-
-
- Database -
-
- - db1 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- -
-
-
- Accelerations -
-
- - -
-
-
- - - - Discover - - - - - - Accelerate - - -
-
-
- Name -
-
- -
-
-
- Database -
-
- - db2 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- -
-
-
- Accelerations -
-
- - -
-
-
- - - - Discover - - - - - - Accelerate - - -
-
-
- Name -
-
- -
-
-
- Database -
-
- - db3 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- -
-
-
- Accelerations -
-
- - -
-
-
- - - - Discover - - - - - - Accelerate - - -
-
-
- Name -
-
- -
-
-
- Database -
-
- - db3 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- - -
-
-
- Accelerations -
-
- - , - - -
-
-
- - - - Discover - - - - - - Accelerate - - -
-
-
- Name -
-
- -
-
-
- Database -
-
- - db3 - -
-
-
- Type -
-
- - Cover Index - -
-
-
- Created by Integration -
-
- - -
-
-
- Accelerations -
-
- - -
-
-
- - - - Discover - - -
-
-
- Name -
-
- -
-
-
- Database -
-
- - db1 - -
-
-
- Type -
-
- - Skip Index - -
-
-
- Created by Integration -
-
- -
-
-
- Accelerations -
-
- - -
-
-
- - - - Discover - - -
-
-
-
-
-
-
-
-
- -
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-`; +exports[`Data Connection Page test Renders Prometheus data connection page with data 1`] = `
`; exports[`Data Connection Page test Renders S3 data connection page with data 1`] = `
@@ -2049,7 +27,9 @@ exports[`Data Connection Page test Renders S3 data connection page with data 1`]

+ > + ya +

@@ -2176,16 +156,24 @@ exports[`Data Connection Page test Renders S3 data connection page with data 1`] >
@@ -2405,20 +393,6 @@ exports[`Data Connection Page test Renders S3 data connection page with data 1`] Accelerations - -
-
-
-
- Last updated at: -
-
-
-
-
-
- Mon, 01 Jan 2024 00:00:00 GMT -
-
+ Last updated at:
-
-
-
-
-
- - - Accelerations recommended for tables. Setup acceleration or configure integrations - -
-
-
-
-
- -
- - - -
+ 1/1/2024, 12:00:00 AM
-
+
+ -
-
-
+ + -
- -
-
-
-
+ Refresh + + +
-
+
+
+
+ -
-
-
-
-
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - Actions - - -
-
- Name -
-
- -
-
-
- Database -
-
- - db1 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- -
-
-
- Accelerations -
-
- - -
-
-
- - - - Discover - - - - - - Accelerate - - -
-
-
- Name -
-
- -
-
-
- Database -
-
- - db1 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- -
-
-
- Accelerations -
-
- - -
-
-
- - - - Discover - - - - - - Accelerate - - -
-
-
- Name -
-
- -
-
-
- Database -
-
- - db2 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- -
-
-
- Accelerations -
-
- - -
-
-
- - - - Discover - - - - - - Accelerate - - -
-
-
- Name -
-
- -
-
-
- Database -
-
- - db3 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- -
-
-
- Accelerations -
-
- - -
-
-
- - - - Discover - - - - - - Accelerate - - -
-
-
- Name -
-
- -
-
-
- Database -
-
- - db3 - -
-
-
- Type -
-
- - Table - -
-
-
- Created by Integration -
-
- - -
-
-
- Accelerations -
-
- - , - - -
-
-
- - - - Discover - - - - - - Accelerate - - -
-
-
- Name -
-
- -
-
-
- Database -
-
- - db3 - -
-
-
- Type -
-
- - Cover Index - -
-
-
- Created by Integration -
-
- - -
-
-
- Accelerations -
-
- - -
-
-
- - - - Discover - - -
-
-
- Name -
-
- -
-
-
- Database -
-
- - db1 - -
-
-
- Type -
-
- - Skip Index - -
-
-
- Created by Integration -
-
- -
-
-
- Accelerations -
-
- - -
-
-
- - - - Discover - - -
-
-
-
-
-
-
-
-
- -
-
-
-
- -
-
+
+

+ Loading + databases +

-
+
{ const actualModule = jest.requireActual('../../../../framework/core_refs'); @@ -52,12 +53,14 @@ jest.mock('../../../../framework/core_refs', () => { }; }); -const mockAcceleration = { - index: 'mockIndex', - dataSourceName: 'mockDataSource', - acceleration: { - flintIndexName: 'testIndex', - }, +const mockAcceleration: CachedAcceleration = { + flintIndexName: 'testIndex', + type: 'materialized', + database: 'mockDatabase', + table: 'mockTable', + indexName: 'mockIndex', + autoRefresh: true, + status: 'Updated', }; configure({ adapter: new Adapter() }); @@ -68,7 +71,13 @@ describe('AccelerationDetailsFlyout Component Tests', () => { }); it('fetches acceleration details on mount', async () => { - mount(); + mount( + + ); expect(coreRefsModule.coreRefs.dslService!.fetchFields).toHaveBeenCalledWith('testIndex'); expect(coreRefsModule.coreRefs.dslService!.fetchSettings).toHaveBeenCalledWith('testIndex'); @@ -76,7 +85,13 @@ describe('AccelerationDetailsFlyout Component Tests', () => { }); it('switches tabs correctly', async () => { - const wrapper = mount(); + const wrapper = mount( + + ); await new Promise(setImmediate); wrapper.update(); diff --git a/public/components/datasources/components/__tests__/acceleration_table.test.tsx b/public/components/datasources/components/__tests__/acceleration_table.test.tsx index 5531d030b..a8f851639 100644 --- a/public/components/datasources/components/__tests__/acceleration_table.test.tsx +++ b/public/components/datasources/components/__tests__/acceleration_table.test.tsx @@ -11,6 +11,7 @@ import { act } from 'react-dom/test-utils'; import Adapter from 'enzyme-adapter-react-16'; import { ACC_LOADING_MSG } from '../manage/accelerations/utils/acceleration_utils'; import { ReactWrapper } from 'enzyme'; +import { DirectQueryLoadingStatus } from '../../../../../common/types/explorer'; const accelerationCache = { accelerations: [ @@ -84,8 +85,17 @@ jest.mock('../../../../plugin', () => ({ describe('AccelerationTable Component', () => { configure({ adapter: new Adapter() }); + const cacheLoadingHooks = { + databasesLoadStatus: DirectQueryLoadingStatus.INITIAL, + tablesLoadStatus: DirectQueryLoadingStatus.INITIAL, + accelerationsLoadStatus: DirectQueryLoadingStatus.INITIAL, + startLoadingAccelerations: jest.fn(), + }; + it('renders without crashing', () => { - const wrapper = mount(); + const wrapper = mount( + + ); expect(wrapper).toBeDefined(); }); @@ -99,7 +109,9 @@ describe('AccelerationTable Component', () => { let wrapper: ReactWrapper; await act(async () => { - wrapper = mount(); + wrapper = mount( + + ); }); wrapper!.update(); @@ -118,7 +130,9 @@ describe('AccelerationTable Component', () => { it('correctly displays accelerations in the table', async () => { let wrapper: ReactWrapper; await act(async () => { - wrapper = mount(); + wrapper = mount( + + ); }); wrapper!.update(); @@ -136,7 +150,9 @@ describe('AccelerationTable Component', () => { let wrapper: ReactWrapper; await act(async () => { - wrapper = mount(); + wrapper = mount( + + ); await new Promise((resolve) => setTimeout(resolve, 0)); wrapper!.update(); }); @@ -154,7 +170,9 @@ describe('AccelerationTable Component', () => { it('displays updated time correctly', async () => { let wrapper: ReactWrapper; await act(async () => { - wrapper = mount(); + wrapper = mount( + + ); }); wrapper!.update(); diff --git a/public/components/datasources/components/__tests__/associated_objects_flyout.test.tsx b/public/components/datasources/components/__tests__/associated_objects_flyout.test.tsx index 256df3945..713cc363a 100644 --- a/public/components/datasources/components/__tests__/associated_objects_flyout.test.tsx +++ b/public/components/datasources/components/__tests__/associated_objects_flyout.test.tsx @@ -7,9 +7,9 @@ import React from 'react'; import { mount, configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import { AssociatedObjectsDetailsFlyout } from '../manage/associated_objects/associated_objects_details_flyout'; -import { mockAssociatedObjects } from '../manage/associated_objects/utils/associated_objects_tab_utils'; import * as plugin from '../../../../plugin'; import { act } from '@testing-library/react'; +import { mockAssociatedObjects } from '../../../../../test/datasources'; configure({ adapter: new Adapter() }); diff --git a/public/components/datasources/components/__tests__/associated_objects_tab.test.tsx b/public/components/datasources/components/__tests__/associated_objects_tab.test.tsx index bad411c4d..e553a5fc8 100644 --- a/public/components/datasources/components/__tests__/associated_objects_tab.test.tsx +++ b/public/components/datasources/components/__tests__/associated_objects_tab.test.tsx @@ -7,10 +7,16 @@ import { configure, mount } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import React from 'react'; import { AssociatedObjectsTab } from '../manage/associated_objects/associated_objects_tab'; +import { ASSC_OBJ_TABLE_SUBJ } from '../manage/associated_objects/utils/associated_objects_tab_utils'; +import { CatalogCacheManager } from '../../../../framework/catalog_cache/cache_manager'; import { - mockAssociatedObjects, - ASSC_OBJ_TABLE_SUBJ, -} from '../manage/associated_objects/utils/associated_objects_tab_utils'; + mockAccelerationCacheData, + mockDataSourceCacheData, + mockDatasource, + mockEmptyAccelerationCacheData, + mockEmptyDataSourceCacheData, +} from '../../../../../test/datasources'; +import { DirectQueryLoadingStatus } from '../../../../../common/types/explorer'; jest.mock('../../../../plugin', () => ({ getRenderAccelerationDetailsFlyout: jest.fn(() => jest.fn()), @@ -20,9 +26,18 @@ jest.mock('../../../../plugin', () => ({ describe('AssociatedObjectsTab Component', () => { configure({ adapter: new Adapter() }); + const cacheLoadingHooks = { + databasesLoadStatus: DirectQueryLoadingStatus.INITIAL, + startLoadingDatabases: jest.fn(), + tablesLoadStatus: DirectQueryLoadingStatus.INITIAL, + startLoadingTables: jest.fn(), + accelerationsLoadStatus: DirectQueryLoadingStatus.INITIAL, + startLoadingAccelerations: jest.fn(), + }; + beforeAll(() => { const originalDate = Date; - global.Date = jest.fn(() => new originalDate('2024-03-06T07:02:37.000Z')) as any; + global.Date = jest.fn(() => new originalDate('2024-03-14T12:00:00Z')) as any; global.Date.UTC = originalDate.UTC; global.Date.parse = originalDate.parse; @@ -33,36 +48,58 @@ describe('AssociatedObjectsTab Component', () => { jest.restoreAllMocks(); }); - it('renders without crashing with no associated objects', () => { - const wrapper = mount(); + it('renders tab with no databases or objects', () => { + CatalogCacheManager.saveDataSourceCache(mockEmptyDataSourceCacheData); + CatalogCacheManager.saveAccelerationsCache(mockEmptyAccelerationCacheData); + const wrapper = mount( + + ); expect(wrapper).toMatchSnapshot(); - expect(wrapper.text()).toContain('You have no associated objects'); + expect(wrapper.text()).toContain('You have no databases in your data source'); }); it('renders correctly with associated objects', () => { - const wrapper = mount(); + CatalogCacheManager.saveDataSourceCache(mockDataSourceCacheData); + CatalogCacheManager.saveAccelerationsCache(mockAccelerationCacheData); + const wrapper = mount( + + ); expect(wrapper).toMatchSnapshot(); expect(wrapper.find('EuiInMemoryTable').exists()).toBe(true); expect(wrapper.find('EuiLink').length).toBeGreaterThan(0); }); it('initializes database and acceleration filter options correctly from associated objects', () => { - const wrapper = mount(); + CatalogCacheManager.saveDataSourceCache(mockDataSourceCacheData); + CatalogCacheManager.saveAccelerationsCache(mockAccelerationCacheData); + const wrapper = mount( + + ); wrapper.update(); const tableProps = wrapper.find(`[data-test-subj="${ASSC_OBJ_TABLE_SUBJ}"]`).first().props(); const { search } = tableProps; - const databaseFilter = search.filters.find((filter) => filter.field === 'database'); const accelerationFilter = search.filters.find((filter) => filter.field === 'accelerations'); - const expectedDatabaseOptionsCount = new Set(mockAssociatedObjects.map((obj) => obj.database)) - .size; - expect(databaseFilter.options.length).toEqual(expectedDatabaseOptionsCount); - - const allAccelerationNames = mockAssociatedObjects.flatMap((obj) => - obj.accelerations.map((acceleration) => acceleration.name) + const allAccelerationNames = mockAccelerationCacheData.dataSources[0].accelerations.flatMap( + (obj) => obj.flintIndexName ); const uniqueAccelerationNames = new Set(allAccelerationNames.filter(Boolean)); const expectedAccelerationOptionsCount = uniqueAccelerationNames.size; @@ -70,15 +107,24 @@ describe('AssociatedObjectsTab Component', () => { }); it('correctly filters associated objects by acceleration name', () => { - const wrapper = mount(); + CatalogCacheManager.saveDataSourceCache(mockDataSourceCacheData); + CatalogCacheManager.saveAccelerationsCache(mockAccelerationCacheData); + const wrapper = mount( + + ); const mockQueryObject = { - queryText: 'accelerations:skipping_index_2', + queryText: 'accelerations:mock_acceleration_1', ast: { _clauses: [ { type: 'term', - value: 'skipping_index_2', + value: 'mock_acceleration_1', field: 'accelerations', }, ], @@ -93,14 +139,7 @@ describe('AssociatedObjectsTab Component', () => { wrapper.update(); const filteredItems = wrapper.find('EuiInMemoryTable').prop('items'); - const expectedFilteredItems = mockAssociatedObjects.filter((obj) => - obj.accelerations.some((acc) => acc.name === 'skipping_index_2') - ); - - expect(filteredItems.length).toEqual(expectedFilteredItems.length); - expectedFilteredItems.forEach((expectedItem) => { - expect(filteredItems.some((item) => item.id === expectedItem.id)).toBeTruthy(); - }); + expect(filteredItems.length).toEqual(1); }); }); diff --git a/public/components/datasources/components/__tests__/data_connection.test.tsx b/public/components/datasources/components/__tests__/data_connection.test.tsx index 1510a93f1..57aab1c7c 100644 --- a/public/components/datasources/components/__tests__/data_connection.test.tsx +++ b/public/components/datasources/components/__tests__/data_connection.test.tsx @@ -12,8 +12,11 @@ import { coreRefs } from '../../../../../public/framework/core_refs'; import { describePrometheusDataConnection, describeS3Dataconnection, + mockAccelerationCacheData, + mockDataSourceCacheData, } from '../../../../../test/datasources'; import { DataConnection } from '../manage/data_connection'; +import { CatalogCacheManager } from '../../../../../public/framework/catalog_cache/cache_manager'; jest.mock('../../../../plugin', () => ({ getRenderAccelerationDetailsFlyout: jest.fn(), @@ -42,25 +45,23 @@ describe('Data Connection Page test', () => { }); it('Renders Prometheus data connection page with data', async () => { - const pplService = { - fetch: jest.fn(), - }; + CatalogCacheManager.saveDataSourceCache(mockDataSourceCacheData); + CatalogCacheManager.saveAccelerationsCache(mockAccelerationCacheData); const container = document.createElement('div'); (coreRefs.http!.get as jest.Mock).mockResolvedValue(describePrometheusDataConnection); await act(() => { - ReactDOM.render(, container); + ReactDOM.render(, container); }); expect(container).toMatchSnapshot(); }); it('Renders S3 data connection page with data', async () => { - const pplService = { - fetch: jest.fn(), - }; + CatalogCacheManager.saveDataSourceCache(mockDataSourceCacheData); + CatalogCacheManager.saveAccelerationsCache(mockAccelerationCacheData); const container = document.createElement('div'); (coreRefs.http!.get as jest.Mock).mockResolvedValue(describeS3Dataconnection); await act(() => { - ReactDOM.render(, container); + ReactDOM.render(, container); }); expect(container).toMatchSnapshot(); }); diff --git a/public/components/datasources/components/manage/accelerations/acceleration_details_flyout.tsx b/public/components/datasources/components/manage/accelerations/acceleration_details_flyout.tsx index 9ec84e1ea..796367136 100644 --- a/public/components/datasources/components/manage/accelerations/acceleration_details_flyout.tsx +++ b/public/components/datasources/components/manage/accelerations/acceleration_details_flyout.tsx @@ -27,9 +27,12 @@ import { } from './utils/acceleration_utils'; import { coreRefs } from '../../../../../framework/core_refs'; import { OpenSearchDashboardsResponse } from '../../../../../../../../src/core/server/http/router'; +import { CachedAcceleration } from '../../../../../../common/types/data_connections'; export interface AccelerationDetailsFlyoutProps { - acceleration: any; + index: string; + acceleration: CachedAcceleration; + dataSourceName: string; } const getMappings = (index: string): Promise | undefined => { @@ -53,10 +56,9 @@ const handleDetailsFetchingPromise = ( .catch((error) => ({ status: 'rejected', action, error })); }; -export const AccelerationDetailsFlyout = ({ - acceleration: selectedAcc, -}: AccelerationDetailsFlyoutProps) => { - const { index, dataSourceName, acceleration } = selectedAcc; +export const AccelerationDetailsFlyout = (props: AccelerationDetailsFlyoutProps) => { + const { index, dataSourceName, acceleration } = props; + console.log(index, acceleration, dataSourceName); const { flintIndexName } = acceleration; const [selectedTab, setSelectedTab] = useState('details'); const tabsMap: { [key: string]: any } = { diff --git a/public/components/datasources/components/manage/accelerations/acceleration_table.tsx b/public/components/datasources/components/manage/accelerations/acceleration_table.tsx index 776a6865b..706925604 100644 --- a/public/components/datasources/components/manage/accelerations/acceleration_table.tsx +++ b/public/components/datasources/components/manage/accelerations/acceleration_table.tsx @@ -18,19 +18,20 @@ import { EuiText, } from '@elastic/eui'; import React, { useEffect, useState } from 'react'; +import { CatalogCacheManager } from '../../../../../framework/catalog_cache/cache_manager'; import { - CachedAccelerations, + CachedAcceleration, CachedDataSourceStatus, } from '../../../../../../common/types/data_connections'; import { DirectQueryLoadingStatus } from '../../../../../../common/types/explorer'; -import { useLoadAccelerationsToCache } from '../../../../../framework/catalog_cache/cache_loader'; -import { CatalogCacheManager } from '../../../../../framework/catalog_cache/cache_manager'; +import { isCatalogCacheFetching } from '../associated_objects/utils/associated_objects_tab_utils'; import { getRenderAccelerationDetailsFlyout } from '../../../../../plugin'; import { ACC_LOADING_MSG, ACC_PANEL_DESC, ACC_PANEL_TITLE, AccelerationStatus, + getAccelerationName, getRefreshButtonIcon, onDeleteButtonClick, onDiscoverButtonClick, @@ -39,24 +40,37 @@ import { interface AccelerationTableProps { dataSourceName: string; + cacheLoadingHooks: any; } -export const AccelerationTable = ({ dataSourceName }: AccelerationTableProps) => { - const [accelerations, setAccelerations] = useState([]); +export const AccelerationTable = ({ + dataSourceName, + cacheLoadingHooks, +}: AccelerationTableProps) => { + const [accelerations, setAccelerations] = useState([]); const [updatedTime, setUpdatedTime] = useState(); - const { loadStatus, startLoading } = useLoadAccelerationsToCache(); + + const { + databasesLoadStatus, + tablesLoadStatus, + accelerationsLoadStatus, + startLoadingAccelerations, + } = cacheLoadingHooks; const [isRefreshing, setIsRefreshing] = useState(false); useEffect(() => { const cachedDataSource = CatalogCacheManager.getOrCreateAccelerationsByDataSource( dataSourceName ); - if (cachedDataSource.status === CachedDataSourceStatus.Empty) { + if ( + cachedDataSource.status === CachedDataSourceStatus.Empty && + !isCatalogCacheFetching(accelerationsLoadStatus) + ) { console.log( `Cache for dataSource ${dataSourceName} is empty or outdated. Loading accelerations...` ); setIsRefreshing(true); - startLoading(dataSourceName); + startLoadingAccelerations(dataSourceName); } else { console.log(`Using cached accelerations for dataSource: ${dataSourceName}`); @@ -66,7 +80,7 @@ export const AccelerationTable = ({ dataSourceName }: AccelerationTableProps) => }, []); useEffect(() => { - if (loadStatus === DirectQueryLoadingStatus.SUCCESS) { + if (accelerationsLoadStatus === DirectQueryLoadingStatus.SUCCESS) { const cachedDataSource = CatalogCacheManager.getOrCreateAccelerationsByDataSource( dataSourceName ); @@ -75,21 +89,29 @@ export const AccelerationTable = ({ dataSourceName }: AccelerationTableProps) => setIsRefreshing(false); console.log('Refresh process is success.'); } - if (loadStatus === DirectQueryLoadingStatus.FAILED) { + if (accelerationsLoadStatus === DirectQueryLoadingStatus.FAILED) { setIsRefreshing(false); console.log('Refresh process is failed.'); } - }, [loadStatus]); + }, [accelerationsLoadStatus]); const handleRefresh = () => { console.log('Initiating refresh...'); - setIsRefreshing(true); - startLoading(dataSourceName); + if (!isCatalogCacheFetching(accelerationsLoadStatus)) { + setIsRefreshing(true); + startLoadingAccelerations(dataSourceName); + } }; const RefreshButton = () => { return ( - + Refresh ); @@ -179,13 +201,12 @@ export const AccelerationTable = ({ dataSourceName }: AccelerationTableProps) => field: 'indexName', name: 'Name', sortable: true, - render: (indexName: string, acceleration: any) => { - const displayName = - indexName || - `${dataSourceName}_${acceleration.database}_${acceleration.table}`.replace(/\s+/g, '_'); + render: (indexName: string, acceleration: CachedAcceleration) => { + const displayName = getAccelerationName(indexName, acceleration, dataSourceName); return ( { + console.log(displayName); renderAccelerationDetailsFlyout({ index: displayName, acceleration, @@ -242,7 +263,7 @@ export const AccelerationTable = ({ dataSourceName }: AccelerationTableProps) => field: 'refreshType', name: 'Refresh Type', sortable: true, - render: (autoRefresh: boolean, acceleration: CachedAccelerations) => { + render: (autoRefresh: boolean, acceleration: CachedAcceleration) => { return {acceleration.autoRefresh ? 'Auto refresh' : 'Manual'}; }, }, @@ -250,7 +271,7 @@ export const AccelerationTable = ({ dataSourceName }: AccelerationTableProps) => field: 'flintIndexName', name: 'Destination Index', sortable: true, - render: (flintIndexName: string, acceleration: CachedAccelerations) => { + render: (flintIndexName: string, acceleration: CachedAcceleration) => { if (acceleration.type === 'skipping') { return '-'; } @@ -270,6 +291,7 @@ export const AccelerationTable = ({ dataSourceName }: AccelerationTableProps) => const sorting = { sort: { + field: 'name', direction: 'asc', }, }; diff --git a/public/components/datasources/components/manage/accelerations/flyout_modules/acceleration_sql_tab.tsx b/public/components/datasources/components/manage/accelerations/flyout_modules/acceleration_sql_tab.tsx index 305cd50ba..4898ab20e 100644 --- a/public/components/datasources/components/manage/accelerations/flyout_modules/acceleration_sql_tab.tsx +++ b/public/components/datasources/components/manage/accelerations/flyout_modules/acceleration_sql_tab.tsx @@ -7,19 +7,19 @@ import { EuiCodeBlock, EuiSpacer } from '@elastic/eui'; import React from 'react'; interface AccelerationSqlTabProps { - acceleration: any; + mappings: any; } export const AccelerationSqlTab = (props: AccelerationSqlTabProps) => { - const { acceleration } = props; + const { mappings } = props; // TODO: Retrieve SQL query from backend - console.log(acceleration); + console.log(mappings); return ( <> - {acceleration.sql} + Placeholder ); diff --git a/public/components/datasources/components/manage/accelerations/utils/acceleration_utils.tsx b/public/components/datasources/components/manage/accelerations/utils/acceleration_utils.tsx index 901279f76..acc8379ee 100644 --- a/public/components/datasources/components/manage/accelerations/utils/acceleration_utils.tsx +++ b/public/components/datasources/components/manage/accelerations/utils/acceleration_utils.tsx @@ -5,12 +5,23 @@ import React from 'react'; import { EuiHealth } from '@elastic/eui'; +import { CachedAcceleration } from '../../../../../../../common/types/data_connections'; export const ACC_PANEL_TITLE = 'Accelerations'; export const ACC_PANEL_DESC = 'Accelerations optimize query performance by indexing external data into OpenSearch.'; export const ACC_LOADING_MSG = 'Loading/Refreshing accelerations...'; +export const getAccelerationName = ( + indexName: string, + acceleration: CachedAcceleration, + datasource: string +) => { + return ( + indexName || `${datasource}_${acceleration.database}_${acceleration.table}`.replace(/\s+/g, '_') + ); +}; + export const AccelerationStatus = ({ status }: { status: string }) => { const label = status; let color; diff --git a/public/components/datasources/components/manage/associated_objects/associated_objects_details_flyout.tsx b/public/components/datasources/components/manage/associated_objects/associated_objects_details_flyout.tsx index ea2d4b776..7a03e4696 100644 --- a/public/components/datasources/components/manage/associated_objects/associated_objects_details_flyout.tsx +++ b/public/components/datasources/components/manage/associated_objects/associated_objects_details_flyout.tsx @@ -108,10 +108,12 @@ export const AssociatedObjectsDetailsFlyout = ({ tableDetail }: AssociatedObject id: index, })); - const schemaData = tableDetail.columns.map((column, index) => ({ - ...column, - id: index, - })); + const schemaData = tableDetail.columns + ? tableDetail.columns.map((column, index) => ({ + ...column, + id: index, + })) + : {}; const accelerationColumns = [ { diff --git a/public/components/datasources/components/manage/associated_objects/associated_objects_tab.tsx b/public/components/datasources/components/manage/associated_objects/associated_objects_tab.tsx index 165023c3f..f39ce90f5 100644 --- a/public/components/datasources/components/manage/associated_objects/associated_objects_tab.tsx +++ b/public/components/datasources/components/manage/associated_objects/associated_objects_tab.tsx @@ -6,104 +6,120 @@ import React, { useEffect, useState } from 'react'; import _ from 'lodash'; import { - EuiInMemoryTable, - EuiLink, EuiPanel, EuiFlexGroup, EuiFlexItem, EuiText, EuiHorizontalRule, - EuiButton, EuiSpacer, - EuiEmptyPrompt, - SearchFilterConfig, - EuiTableFieldDataColumnType, + EuiSelectable, } from '@elastic/eui'; -import { AssociatedObject } from 'common/types/data_connections'; import { i18n } from '@osd/i18n'; +import { ACCELERATION_INDEX_TYPES } from '../../../../../../common/constants/data_sources'; import { - getRenderAccelerationDetailsFlyout, - getRenderAssociatedObjectsDetailsFlyout, -} from '../../../../../plugin'; + AssociatedObject, + CachedAcceleration, + CachedAccelerationByDataSource, + CachedDataSourceStatus, + CachedDatabase, + CachedTable, + DatasourceDetails, +} from '../../../../../../common/types/data_connections'; import { AccelerationsRecommendationCallout } from './accelerations_recommendation_callout'; import { - ASSC_OBJ_TABLE_ACC_COLUMN_NAME, - ASSC_OBJ_TABLE_SEARCH_HINT, ASSC_OBJ_PANEL_TITLE, - ASSC_OBJ_PANEL_DESRIPTION, - ASSC_OBJ_NO_DATA_TITLE, - ASSC_OBJ_NO_DATA_DESCRIPTION, - ASSC_OBJ_REFRESH_BTN, + ASSC_OBJ_PANEL_DESCRIPTION, ASSC_OBJ_FRESH_MSG, - ASSC_OBJ_TABLE_SUBJ, + isCatalogCacheFetching, } from './utils/associated_objects_tab_utils'; +import { DirectQueryLoadingStatus } from '../../../../../../common/types/explorer'; +import { AssociatedObjectsTabEmpty } from './utils/associated_objects_tab_empty'; +import { AssociatedObjectsTabLoading } from './utils/associated_objects_tab_loading'; +import { AssociatedObjectsRefreshButton } from './utils/associated_objects_refresh_button'; +import { CatalogCacheManager } from '../../../../../../public/framework/catalog_cache/cache_manager'; +import { AssociatedObjectsTable } from './modules/associated_objects_table'; +import { getAccelerationName } from '../accelerations/utils/acceleration_utils'; export interface AssociatedObjectsTabProps { - associatedObjects: AssociatedObject[]; + datasource: DatasourceDetails; + cacheLoadingHooks: any; + selectedDatabase: string; + setSelectedDatabase: React.Dispatch>; } -interface FilterOption { - value: string; - text: string; -} +export const AssociatedObjectsTab: React.FC = (props) => { + const { datasource, cacheLoadingHooks, selectedDatabase, setSelectedDatabase } = props; + const [isRefreshing, setIsRefreshing] = useState(false); + const [lastUpdated, setLastUpdated] = useState(new Date().toLocaleString()); + const [isObjectsLoading, setIsObjectsLoading] = useState(false); + const [cachedDatabases, setCachedDatabases] = useState([]); + const [cachedTables, setCachedTables] = useState([]); + const [cachedAccelerations, setCachedAccelerations] = useState([]); + const [associatedObjects, setAssociatedObjects] = useState([]); + const [isFirstTimeLoading, setIsFirstTimeLoading] = useState(true); -interface AssociatedTableFilter { - type: string; - field: string; - operator: string; - value: string; -} + const { + databasesLoadStatus, + startLoadingDatabases, + tablesLoadStatus, + startLoadingTables, + accelerationsLoadStatus, + startLoadingAccelerations, + } = cacheLoadingHooks; -export const AssociatedObjectsTab: React.FC = ({ - associatedObjects, -}) => { - const [lastUpdated, setLastUpdated] = useState(''); - const [databaseFilterOptions, setDatabaseFilterOptions] = useState([]); - const [accelerationFilterOptions, setAccelerationFilterOptions] = useState([]); - const [filteredObjects, setFilteredObjects] = useState([]); + let lastChecked: boolean; + if (selectedDatabase !== '') { + lastChecked = true; + } else { + lastChecked = false; + } + // Get last selected if there is one, set to first option if not + const [databaseSelectorOptions, setDatabaseSelectorOptions] = useState( + cachedDatabases.map((database, index) => { + return { + label: database.name, + checked: lastChecked + ? database.name === selectedDatabase + ? 'on' + : index === 0 + ? 'on' + : undefined + : undefined, + }; + }) + ); - // TODO: FINISH THE REFRESH LOGIC - const fetchAssociatedObjects = async () => { - // Placeholder for data fetching logic - // After fetching data: - // setAssociatedObjects(fetchedData); - const now = new Date(); - setLastUpdated(now.toUTCString()); // Update last updated time + const onRefreshButtonClick = () => { + if (!isCatalogCacheFetching(databasesLoadStatus, tablesLoadStatus, accelerationsLoadStatus)) { + startLoadingDatabases(datasource.name); + setIsRefreshing(true); + } }; - useEffect(() => { - fetchAssociatedObjects(); - - const databaseOptions = Array.from(new Set(associatedObjects.map((obj) => obj.database))) - .sort() - .map((database) => ({ value: database, text: database })); - setDatabaseFilterOptions(databaseOptions); - - const accelerationOptions = Array.from( - new Set( - associatedObjects - .flatMap((obj) => obj.accelerations.map((acceleration) => acceleration.name)) - .filter(Boolean) - ) - ) - .sort() - .map((name) => ({ value: name, text: name })); - setAccelerationFilterOptions(accelerationOptions); - - setFilteredObjects(associatedObjects); - }, [associatedObjects]); - const AssociatedObjectsHeader = () => { const panelTitle = i18n.translate('datasources.associatedObjectsTab.panelTitle', { defaultMessage: ASSC_OBJ_PANEL_TITLE, }); const panelDescription = i18n.translate('datasources.associatedObjectsTab.panelDescription', { - defaultMessage: ASSC_OBJ_PANEL_DESRIPTION, + defaultMessage: ASSC_OBJ_PANEL_DESCRIPTION, }); + const LastUpdatedText = () => { + return ( + <> + + {ASSC_OBJ_FRESH_MSG} + + + {lastUpdated} + + + ); + }; + return ( - +

{panelTitle}

@@ -111,267 +127,219 @@ export const AssociatedObjectsTab: React.FC = ({
- - - - {ASSC_OBJ_REFRESH_BTN} - - - - - {ASSC_OBJ_FRESH_MSG} - - - {lastUpdated} - - - + + + +
); }; - const noDataMessage = ( - - {i18n.translate('datasources.associatedObjectsTab.noDataTitle', { - defaultMessage: ASSC_OBJ_NO_DATA_TITLE, - })} - - } - body={ -

- {i18n.translate('datasources.associatedObjectsTab.noDataDescription', { - defaultMessage: ASSC_OBJ_NO_DATA_DESCRIPTION, - })} -

- } - actions={ - window.open('https://example.com', '_blank')} - iconType="popout" - iconSide="left" - > - {i18n.translate('datasources.associatedObjectsTab.queryWorkbenchButton', { - defaultMessage: 'Query Workbench', - })} - + // Load databases if empty or retrieve from cache if updated + useEffect(() => { + if (datasource.name) { + const datasourceCache = CatalogCacheManager.getOrCreateDataSource(datasource.name); + if ( + datasourceCache.status === CachedDataSourceStatus.Empty && + !isCatalogCacheFetching(databasesLoadStatus) + ) { + startLoadingDatabases(datasource.name); + } else if (datasourceCache.status === CachedDataSourceStatus.Updated) { + setCachedDatabases(datasourceCache.databases); + setIsFirstTimeLoading(false); } - /> - ); - - const columns = [ - { - field: 'name', - name: i18n.translate('datasources.associatedObjectsTab.column.name', { - defaultMessage: 'Name', - }), - sortable: true, - 'data-test-subj': 'nameCell', - render: (name: string, item: AssociatedObject) => ( - { - if (item.type === 'Table') { - renderAssociatedObjectsDetailsFlyout(item); - } else { - renderAccelerationDetailsFlyout(item.accelerations[0]); - } - }} - > - {name} - - ), - }, - { - field: 'database', - name: i18n.translate('datasources.associatedObjectsTab.column.database', { - defaultMessage: 'Database', - }), - sortable: true, - }, - { - field: 'type', - name: i18n.translate('datasources.associatedObjectsTab.column.type', { - defaultMessage: 'Type', - }), - sortable: true, - }, - { - field: 'createdByIntegration', - name: i18n.translate('datasources.associatedObjectsTab.column.createdByIntegration', { - defaultMessage: 'Created by Integration', - }), - sortable: true, - render: (createdByIntegration: string, _item: AssociatedObject) => - createdByIntegration ? ( - openDetailsPage(createdByIntegration)}> - {createdByIntegration} - - ) : ( - '-' - ), - }, - { - field: 'accelerations', - name: i18n.translate('datasources.associatedObjectsTab.column.accelerations', { - defaultMessage: 'Accelerations', - }), - sortable: true, - render: (accelerations: string[]) => { - return accelerations.length > 0 - ? accelerations.map((acceleration, index) => ( - - renderAccelerationDetailsFlyout(acceleration)}> - {acceleration.name} - - {index < accelerations.length - 1 ? ', ' : ''} - - )) - : '-'; - }, - }, - { - name: i18n.translate('datasources.associatedObjectsTab.column.actions', { - defaultMessage: 'Actions', - }), - actions: [ - { - name: i18n.translate('datasources.associatedObjectsTab.action.discover.name', { - defaultMessage: 'Discover', - }), - description: i18n.translate( - 'datasources.associatedObjectsTab.action.discover.description', - { - defaultMessage: 'Discover this object', - } - ), - type: 'icon', - icon: 'discoverApp', - onClick: (item: AssociatedObject) => console.log('Discover', item), - }, - { - name: i18n.translate('datasources.associatedObjectsTab.action.accelerate.name', { - defaultMessage: 'Accelerate', - }), - description: i18n.translate( - 'datasources.associatedObjectsTab.action.accelerate.description', - { - defaultMessage: 'Accelerate this object', - } - ), - type: 'icon', - icon: 'bolt', - available: (item: AssociatedObject) => item.type === 'Table', - onClick: (item: AssociatedObject) => console.log('Accelerate', item), - }, - ], - }, - ] as Array>; - - const onSearchChange = ({ query, error }) => { - if (error) { - console.log('Search error:', error); - return; } + }, [datasource.name]); - const matchesClauses = (obj: AssociatedObject, clauses: AssociatedTableFilter[]): boolean => { - if (clauses.length === 0) return true; - - return clauses.some((clause) => { - if (clause.field !== ASSC_OBJ_TABLE_ACC_COLUMN_NAME) { - return obj[clause.field] === clause.value; - } else if ( - clause.field === ASSC_OBJ_TABLE_ACC_COLUMN_NAME && - Array.isArray(obj.accelerations) - ) { - return obj.accelerations.some((acceleration) => acceleration.name === clause.value); - } - - return false; - }); - }; - - const filtered = associatedObjects.filter((obj) => { - const clauses = query.ast._clauses; - return matchesClauses(obj, clauses); - }); + // Retrieve from cache upon load success + useEffect(() => { + const status = databasesLoadStatus.toLowerCase(); + const datasourceCache = CatalogCacheManager.getOrCreateDataSource(datasource.name); + if (status === DirectQueryLoadingStatus.SUCCESS) { + setCachedDatabases(datasourceCache.databases); + setIsFirstTimeLoading(false); + } + }, [datasource.name, databasesLoadStatus]); - setFilteredObjects(filtered); + const handleObjectsLoad = ( + databaseCache: CachedDatabase, + accelerationsCache: CachedAccelerationByDataSource + ) => { + if ( + databaseCache.status === CachedDataSourceStatus.Updated && + accelerationsCache.status === CachedDataSourceStatus.Updated + ) { + setLastUpdated(new Date(databaseCache.lastUpdated).toLocaleString()); + setIsRefreshing(false); + setIsObjectsLoading(false); + } }; - const searchFilters = [ - { - type: 'field_value_selection', - field: 'database', - name: 'Database', - multiSelect: true, - options: databaseFilterOptions, - cache: 60000, - }, - { - type: 'field_value_selection', - field: 'accelerations', - name: 'Accelerations', - multiSelect: true, - options: accelerationFilterOptions, - cache: 60000, - }, - ] as SearchFilterConfig[]; + // Load tables and accelerations if empty or retrieve from cache if not + useEffect(() => { + if (datasource.name && selectedDatabase) { + const databaseCache = CatalogCacheManager.getDatabase(datasource.name, selectedDatabase); + const accelerationsCache = CatalogCacheManager.getOrCreateAccelerationsByDataSource( + datasource.name + ); + if ( + databaseCache.status === CachedDataSourceStatus.Empty && + !isCatalogCacheFetching(tablesLoadStatus) + ) { + startLoadingTables(datasource.name, selectedDatabase); + setIsObjectsLoading(true); + } else if (databaseCache.status === CachedDataSourceStatus.Updated) { + setCachedTables(databaseCache.tables); + } + if ( + accelerationsCache.status === CachedDataSourceStatus.Empty && + !isCatalogCacheFetching(accelerationsLoadStatus) + ) { + startLoadingAccelerations(datasource.name); + setIsObjectsLoading(true); + } else if (accelerationsCache.status === CachedDataSourceStatus.Updated) { + setCachedAccelerations(accelerationsCache.accelerations); + } + } + }, [datasource.name, selectedDatabase, databaseSelectorOptions]); - const search = { - filters: searchFilters, - box: { - incremental: true, - placeholder: ASSC_OBJ_TABLE_SEARCH_HINT, - schema: { - fields: { name: { type: 'string' }, database: { type: 'string' } }, - }, - }, - onChange: onSearchChange, - }; + // Retrieve from tables cache upon load success + useEffect(() => { + if (datasource.name && selectedDatabase) { + const tablesStatus = tablesLoadStatus.toLowerCase(); + const databaseCache = CatalogCacheManager.getDatabase(datasource.name, selectedDatabase); + const accelerationsStatus = accelerationsLoadStatus.toLowerCase(); + const accelerationsCache = CatalogCacheManager.getOrCreateAccelerationsByDataSource( + datasource.name + ); + if (tablesStatus === DirectQueryLoadingStatus.SUCCESS) { + setCachedTables(databaseCache.tables); + } + if (accelerationsStatus === DirectQueryLoadingStatus.SUCCESS) { + setCachedAccelerations(accelerationsCache.accelerations); + } + handleObjectsLoad(databaseCache, accelerationsCache); + } + }, [datasource.name, selectedDatabase, tablesLoadStatus, accelerationsLoadStatus]); - const pagination = { - initialPageSize: 10, - pageSizeOptions: [10, 25, 50], - }; + useEffect(() => { + setDatabaseSelectorOptions( + cachedDatabases.map((database, index) => { + if (selectedDatabase) { + return { + label: database.name, + checked: database.name === selectedDatabase ? 'on' : undefined, + }; + } + return { + label: database.name, + checked: index === 0 ? 'on' : undefined, + }; + }) + ); + }, [cachedDatabases]); - const sorting = { - sort: { - field: 'name', - direction: 'asc', - }, - }; + useEffect(() => { + setSelectedDatabase((prevState) => { + const select = databaseSelectorOptions.find((option) => option.checked === 'on')?.label; + if (select) { + return select; + } + return prevState; + }); + }, [databaseSelectorOptions]); - const renderAccelerationDetailsFlyout = getRenderAccelerationDetailsFlyout(); - const renderAssociatedObjectsDetailsFlyout = getRenderAssociatedObjectsDetailsFlyout(); + useEffect(() => { + const tableObjects: AssociatedObject[] = cachedTables.map((table: CachedTable) => { + return { + datasource: datasource.name, + id: table.name, + name: table.name, + database: selectedDatabase, + type: 'table', + // Temporary dummy array + accelerations: [], + columns: table.columns, + }; + }); + const accelerationObjects: AssociatedObject[] = cachedAccelerations + .filter((acceleration: CachedAcceleration) => acceleration.database === selectedDatabase) + .map((acceleration: CachedAcceleration) => ({ + datasource: datasource.name, + id: getAccelerationName(acceleration.indexName, acceleration, datasource.name), + name: getAccelerationName(acceleration.indexName, acceleration, datasource.name), + database: acceleration.database, + type: ACCELERATION_INDEX_TYPES.find((accelType) => accelType.value === acceleration.type)! + .label, + // Temporary dummy array + accelerations: [], + columns: undefined, + })); + setAssociatedObjects([...tableObjects, ...accelerationObjects]); + }, [selectedDatabase, cachedTables, cachedAccelerations]); return ( <> + + - - - {associatedObjects.length > 0 ? ( - + {isFirstTimeLoading ? ( + ) : ( - noDataMessage + <> + {cachedDatabases.length === 0 ? ( + + ) : ( + <> + + + + setDatabaseSelectorOptions(newOptions)} + > + {(list, search) => ( + <> + {search} + {list} + + )} + + + + {isObjectsLoading && !isRefreshing ? ( + + ) : ( + <> + {cachedTables.length > 0 || cachedAccelerations.length > 0 ? ( + + ) : ( + + )} + + )} + + + + )} + )} diff --git a/public/components/datasources/components/manage/associated_objects/modules/associated_objects_table.tsx b/public/components/datasources/components/manage/associated_objects/modules/associated_objects_table.tsx new file mode 100644 index 000000000..ba74b5134 --- /dev/null +++ b/public/components/datasources/components/manage/associated_objects/modules/associated_objects_table.tsx @@ -0,0 +1,255 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useEffect, useState } from 'react'; +import _ from 'lodash'; +import { + EuiInMemoryTable, + EuiLink, + SearchFilterConfig, + EuiTableFieldDataColumnType, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { + AssociatedObject, + CachedAcceleration, +} from '../../../../../../../common/types/data_connections'; +import { + getRenderAccelerationDetailsFlyout, + getRenderAssociatedObjectsDetailsFlyout, +} from '../../../../../../plugin'; +import { + ASSC_OBJ_TABLE_ACC_COLUMN_NAME, + ASSC_OBJ_TABLE_SEARCH_HINT, + ASSC_OBJ_TABLE_SUBJ, +} from '../utils/associated_objects_tab_utils'; +import { getAccelerationName } from '../../accelerations/utils/acceleration_utils'; + +interface AssociatedObjectsTableProps { + datasourceName: string; + associatedObjects: AssociatedObject[]; + cachedAccelerations: CachedAcceleration[]; +} + +interface FilterOption { + value: string; + text: string; +} + +interface AssociatedTableFilter { + type: string; + field: string; + operator: string; + value: string; +} + +export const AssociatedObjectsTable = (props: AssociatedObjectsTableProps) => { + const { datasourceName, associatedObjects, cachedAccelerations } = props; + const [accelerationFilterOptions, setAccelerationFilterOptions] = useState([]); + const [filteredObjects, setFilteredObjects] = useState([]); + + const columns = [ + { + field: 'name', + name: i18n.translate('datasources.associatedObjectsTab.column.name', { + defaultMessage: 'Name', + }), + sortable: true, + 'data-test-subj': 'nameCell', + render: (name: string, item: AssociatedObject) => ( + { + if (item.type === 'table') { + renderAssociatedObjectsDetailsFlyout(item); + } else { + const acceleration = cachedAccelerations.find( + (acc) => getAccelerationName(acc.indexName, acc, datasourceName) === name + ); + console.log(acceleration); + renderAccelerationDetailsFlyout({ + indexName: getAccelerationName( + acceleration?.indexName, + acceleration, + datasourceName + ), + acceleration, + dataSourceName: datasourceName, + }); + } + }} + > + {name} + + ), + }, + { + field: 'database', + name: i18n.translate('datasources.associatedObjectsTab.column.database', { + defaultMessage: 'Database', + }), + sortable: true, + }, + { + field: 'type', + name: i18n.translate('datasources.associatedObjectsTab.column.type', { + defaultMessage: 'Type', + }), + sortable: true, + }, + { + field: 'accelerations', + name: i18n.translate('datasources.associatedObjectsTab.column.accelerations', { + defaultMessage: 'Accelerations', + }), + sortable: true, + render: (accelerations: string[]) => { + return accelerations.length > 0 + ? accelerations.map((acceleration, index) => ( + + renderAccelerationDetailsFlyout(acceleration)}> + {acceleration.name} + + {index < accelerations.length - 1 ? ', ' : ''} + + )) + : '-'; + }, + }, + { + name: i18n.translate('datasources.associatedObjectsTab.column.actions', { + defaultMessage: 'Actions', + }), + actions: [ + { + name: i18n.translate('datasources.associatedObjectsTab.action.discover.name', { + defaultMessage: 'Discover', + }), + description: i18n.translate( + 'datasources.associatedObjectsTab.action.discover.description', + { + defaultMessage: 'Discover this object', + } + ), + type: 'icon', + icon: 'discoverApp', + onClick: (item: AssociatedObject) => console.log('Discover', item), + }, + { + name: i18n.translate('datasources.associatedObjectsTab.action.accelerate.name', { + defaultMessage: 'Accelerate', + }), + description: i18n.translate( + 'datasources.associatedObjectsTab.action.accelerate.description', + { + defaultMessage: 'Accelerate this object', + } + ), + type: 'icon', + icon: 'bolt', + available: (item: AssociatedObject) => item.type === 'Table', + onClick: (item: AssociatedObject) => console.log('Accelerate', item), + }, + ], + }, + ] as Array>; + + const onSearchChange = ({ query, error }) => { + if (error) { + console.log('Search error:', error); + return; + } + + const matchesClauses = ( + associatedObject: AssociatedObject, + clauses: AssociatedTableFilter[] + ): boolean => { + if (clauses.length === 0) return true; + + return clauses.some((clause) => { + if (clause.field !== ASSC_OBJ_TABLE_ACC_COLUMN_NAME) { + return associatedObject[clause.field] === clause.value; + } else if ( + clause.field === ASSC_OBJ_TABLE_ACC_COLUMN_NAME && + Array.isArray(associatedObject.accelerations) + ) { + return associatedObject.type !== 'table' && associatedObject.name === clause.value; + } + + return false; + }); + }; + + const filtered = associatedObjects.filter((obj) => { + const clauses = query.ast._clauses; + return matchesClauses(obj, clauses); + }); + + setFilteredObjects(filtered); + }; + + const searchFilters = [ + { + type: 'field_value_selection', + field: 'accelerations', + name: 'Accelerations', + multiSelect: true, + options: accelerationFilterOptions, + cache: 60000, + }, + ] as SearchFilterConfig[]; + + const tableSearch = { + filters: searchFilters, + box: { + incremental: true, + placeholder: ASSC_OBJ_TABLE_SEARCH_HINT, + schema: { + fields: { name: { type: 'string' }, database: { type: 'string' } }, + }, + }, + onChange: onSearchChange, + }; + + const pagination = { + initialPageSize: 10, + pageSizeOptions: [10, 25, 50], + }; + + const sorting = { + sort: { + field: 'name', + direction: 'asc', + }, + }; + + useEffect(() => { + const accelerationOptions = Array.from( + new Set( + associatedObjects + .filter((obj) => obj.type !== 'table') + .flatMap((obj) => obj.name) + .filter(Boolean) + ) + ) + .sort() + .map((name) => ({ value: name, text: name })); + setAccelerationFilterOptions(accelerationOptions); + setFilteredObjects(associatedObjects); + }, [associatedObjects]); + + const renderAccelerationDetailsFlyout = getRenderAccelerationDetailsFlyout(); + const renderAssociatedObjectsDetailsFlyout = getRenderAssociatedObjectsDetailsFlyout(); + + return ( + + ); +}; diff --git a/public/components/datasources/components/manage/associated_objects/utils/associated_objects_refresh_button.tsx b/public/components/datasources/components/manage/associated_objects/utils/associated_objects_refresh_button.tsx new file mode 100644 index 000000000..e737a8356 --- /dev/null +++ b/public/components/datasources/components/manage/associated_objects/utils/associated_objects_refresh_button.tsx @@ -0,0 +1,25 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiButton } from '@elastic/eui'; +import React from 'react'; +import { ASSC_OBJ_REFRESH_BTN } from './associated_objects_tab_utils'; + +interface AssociatedObjectsRefreshButtonProps { + isLoading: boolean; + onClick: () => void; +} + +export const AssociatedObjectsRefreshButton: React.FC = ( + props +) => { + const { isLoading, onClick } = props; + + return ( + + {ASSC_OBJ_REFRESH_BTN} + + ); +}; diff --git a/public/components/datasources/components/manage/associated_objects/utils/associated_objects_tab_empty.tsx b/public/components/datasources/components/manage/associated_objects/utils/associated_objects_tab_empty.tsx new file mode 100644 index 000000000..b34575bc0 --- /dev/null +++ b/public/components/datasources/components/manage/associated_objects/utils/associated_objects_tab_empty.tsx @@ -0,0 +1,62 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiButton, EuiEmptyPrompt, EuiLink, EuiText } from '@elastic/eui'; +import React from 'react'; +import { LoadCacheType } from '../../../../../../../common/types/data_connections'; +import { coreRefs } from '../../../../../../framework/core_refs'; + +interface AssociatedObjectsTabEmptyProps { + cacheType: LoadCacheType; +} + +export const AssociatedObjectsTabEmpty: React.FC = (props) => { + const { cacheType } = props; + const { application } = coreRefs; + + const QueryWorkbenchButton = ( + application!.navigateToApp('opensearch-query-workbench')} + iconType="popout" + > + Query Workbench + + ); + + let titleText; + let bodyText; + switch (cacheType) { + case 'databases': + titleText = 'You have no databases in your data source'; + bodyText = 'Add databases and tables to your data source or use Query Workbench'; + break; + case 'tables': + titleText = 'You have no associated objects'; + bodyText = 'Add tables to your data source or use Query Workbench'; + break; + default: + titleText = ''; + bodyText = ''; + break; + } + + return ( + + +

{titleText}

+

{bodyText}

+
+ console.log()} external> + Learn more + + + } + button={QueryWorkbenchButton} + /> + ); +}; diff --git a/public/components/datasources/components/manage/associated_objects/utils/associated_objects_tab_failure.tsx b/public/components/datasources/components/manage/associated_objects/utils/associated_objects_tab_failure.tsx new file mode 100644 index 000000000..8bf05a293 --- /dev/null +++ b/public/components/datasources/components/manage/associated_objects/utils/associated_objects_tab_failure.tsx @@ -0,0 +1,17 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiEmptyPrompt } from '@elastic/eui'; +import React from 'react'; + +export const AssociatedObjectsTabEmpty: React.FC = () => { + return ( + Error} + body={

There was an error loading your databases.

} + /> + ); +}; diff --git a/public/components/datasources/components/manage/associated_objects/utils/associated_objects_tab_loading.tsx b/public/components/datasources/components/manage/associated_objects/utils/associated_objects_tab_loading.tsx new file mode 100644 index 000000000..edfc872ca --- /dev/null +++ b/public/components/datasources/components/manage/associated_objects/utils/associated_objects_tab_loading.tsx @@ -0,0 +1,25 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; +import React from 'react'; + +interface AssociatedObjectsTabLoadingProps { + objectType: string; + warningMessage: boolean; +} + +export const AssociatedObjectsTabLoading: React.FC = (props) => { + const { objectType, warningMessage } = props; + + const BodyText = ( + <> +

Loading {objectType}

+ {warningMessage ?

This may take a moment.

: <>} + + ); + + return } body={BodyText} />; +}; diff --git a/public/components/datasources/components/manage/associated_objects/utils/associated_objects_tab_utils.tsx b/public/components/datasources/components/manage/associated_objects/utils/associated_objects_tab_utils.tsx index f5bb03565..28942265e 100644 --- a/public/components/datasources/components/manage/associated_objects/utils/associated_objects_tab_utils.tsx +++ b/public/components/datasources/components/manage/associated_objects/utils/associated_objects_tab_utils.tsx @@ -3,221 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -export const mockAssociatedObjects = [ - { - id: '1', - datasource: 'flint_s3', - name: 'Table_name_1', - database: 'db1', - type: 'Table', - createdByIntegration: 'integration_1', - accelerations: [ - { - name: 'skipping_index_1', - status: 'ACTIVE', - type: 'skip', - database: 'db1', - table: 'Table_name_1', - destination: 'N/A', - dateCreated: 1709339290, - dateUpdated: 1709339290, - index: 'security_logs_2022', - sql: 'SELECT * FROM Table_name_1 WHERE ...', - }, - ], - columns: [ - { - name: 'column1', - dataType: 'dataType1', - }, - { - name: 'column2', - dataType: 'dataType2', - }, - { - name: 'column3', - dataType: 'dataType3', - }, - { - name: 'column4', - dataType: 'dataType4', - }, - ], - }, - { - id: '2', - datasource: 'flint_s3', - name: 'Table_name_2', - database: 'db1', - type: 'Table', - createdByIntegration: 'integration_1', - accelerations: [ - { - name: 'skipping_index_2', - status: 'ACTIVE', - type: 'skip', - database: 'db1', - table: 'Table_name_1', - destination: 'N/A', - dateCreated: 1709339290, - dateUpdated: 1709339290, - index: 'security_logs_2022', - sql: 'SELECT * FROM Table_name_1 WHERE ...', - }, - ], - columns: [], - }, - { - id: '3', - datasource: 'flint_s3', - name: 'skipping_index_2', - database: 'db1', - type: 'Skip Index', - createdByIntegration: 'integration_1', - accelerations: [ - { - name: 'skipping_index_2', - status: 'ACTIVE', - type: 'skip', - database: 'db1', - table: 'Table_name_1', - destination: 'N/A', - dateCreated: 1709339290, - dateUpdated: 1709339290, - index: 'security_logs_2022', - sql: 'SELECT * FROM Table_name_1 WHERE ...', - }, - ], - columns: [], - }, - { - id: '4', - datasource: 'flint_s3', - name: 'Table_name_4', - database: 'db2', - type: 'Table', - createdByIntegration: 'integration_1', - accelerations: [ - { - name: 'skipping_index_2', - status: 'ACTIVE', - type: 'skip', - database: 'db1', - table: 'Table_name_1', - destination: 'N/A', - dateCreated: 1709339290, - dateUpdated: 1709339290, - index: 'security_logs_2022', - sql: 'SELECT * FROM Table_name_1 WHERE ...', - }, - ], - columns: [ - { - name: 'column1', - dataType: 'dataType1', - }, - { - name: 'column2', - dataType: 'dataType2', - }, - ], - }, - { - id: '5', - datasource: 'flint_s3', - name: 'Table_name_5', - database: 'db3', - type: 'Table', - createdByIntegration: 'integration_1', - accelerations: [], - columns: [ - { - name: 'column1', - dataType: 'dataType1', - }, - { - name: 'column2', - dataType: 'dataType2', - }, - ], - }, - { - id: '6', - datasource: 'flint_s3', - name: 'covering_index_3', - database: 'db3', - type: 'Cover Index', - createdByIntegration: '', - accelerations: [ - { - name: 'covering_index_3', - status: 'ACTIVE', - type: 'skip', - database: 'db1', - table: 'Table_name_1', - destination: 'N/A', - dateCreated: 1709339290, - dateUpdated: 1709339290, - index: 'security_logs_2022', - sql: 'SELECT * FROM Table_name_1 WHERE ...', - }, - ], - columns: [ - { - name: 'column1', - dataType: 'dataType1', - }, - { - name: 'column2', - dataType: 'dataType2', - }, - ], - }, - { - id: '7', - datasource: 'flint_s3', - name: 'Table_name_6', - database: 'db3', - type: 'Table', - createdByIntegration: '', - accelerations: [ - { - name: 'skipping_index_4', - status: 'ACTIVE', - type: 'skip', - database: 'db1', - table: 'Table_name_1', - destination: 'N/A', - dateCreated: 1709339290, - dateUpdated: 1709339290, - index: 'security_logs_2022', - sql: 'SELECT * FROM Table_name_1 WHERE ...', - }, - { - name: 'skipping_index_5', - status: 'ACTIVE', - type: 'skip', - database: 'db1', - table: 'Table_name_1', - destination: 'N/A', - dateCreated: 1709339290, - dateUpdated: 1709339290, - index: 'security_logs_2022', - sql: 'SELECT * FROM Table_name_1 WHERE ...', - }, - ], - columns: [ - { - name: 'column1', - dataType: 'dataType1', - }, - { - name: 'column2', - dataType: 'dataType2', - }, - ], - }, -]; +import { DirectQueryLoadingStatus } from '../../../../../../../common/types/explorer'; export const ASSC_OBJ_TABLE_SUBJ = 'associatedObjectsTable'; @@ -228,7 +14,7 @@ export const ASSC_OBJ_TABLE_SEARCH_HINT = export const ASSC_OBJ_PANEL_TITLE = 'Associated objects'; -export const ASSC_OBJ_PANEL_DESRIPTION = 'Manage objects associated with this data sources.'; +export const ASSC_OBJ_PANEL_DESCRIPTION = 'Manage objects associated with this data sources.'; export const ASSC_OBJ_NO_DATA_TITLE = 'You have no associated objects'; @@ -259,3 +45,15 @@ export const onDeleteButtonClick = (tableDetail: any) => { // TODO: delete table console.log('deleting', tableDetail.name); }; + +const catalogCacheFetchingStatus = [ + DirectQueryLoadingStatus.RUNNING, + DirectQueryLoadingStatus.WAITING, + DirectQueryLoadingStatus.SCHEDULED, +]; + +export const isCatalogCacheFetching = (...statuses: DirectQueryLoadingStatus[]) => { + return statuses.some((status: DirectQueryLoadingStatus) => + catalogCacheFetchingStatus.includes(status) + ); +}; diff --git a/public/components/datasources/components/manage/data_connection.tsx b/public/components/datasources/components/manage/data_connection.tsx index 4fa2cd9f0..30321b89c 100644 --- a/public/components/datasources/components/manage/data_connection.tsx +++ b/public/components/datasources/components/manage/data_connection.tsx @@ -26,31 +26,22 @@ import { observabilityLogsID, observabilityMetricsID, } from '../../../../../common/constants/shared'; -import { DatasourceType } from '../../../../../common/types/data_connections'; import { coreRefs } from '../../../../framework/core_refs'; import { getRenderCreateAccelerationFlyout } from '../../../../plugin'; import { NoAccess } from '../no_access'; +import { + DatasourceDetails, + PrometheusProperties, +} from '../../../../../common/types/data_connections'; +import { AssociatedObjectsTab } from './associated_objects/associated_objects_tab'; import { AccelerationTable } from './accelerations/acceleration_table'; import { AccessControlTab } from './access_control_tab'; -import { AssociatedObjectsTab } from './associated_objects/associated_objects_tab'; -import { mockAssociatedObjects } from './associated_objects/utils/associated_objects_tab_utils'; - -interface DatasourceDetails { - allowedRoles: string[]; - name: string; - connector: DatasourceType; - description: string; - properties: S3GlueProperties | PrometheusProperties; -} - -export interface S3GlueProperties { - 'glue.indexstore.opensearch.uri': string; - 'glue.indexstore.opensearch.region': string; -} +import { + useLoadAccelerationsToCache, + useLoadDatabasesToCache, + useLoadTablesToCache, +} from '../../../../../public/framework/catalog_cache/cache_loader'; -export interface PrometheusProperties { - 'prometheus.uri': string; -} const renderCreateAccelerationFlyout = getRenderCreateAccelerationFlyout(); export const DataConnection = (props: any) => { @@ -64,6 +55,26 @@ export const DataConnection = (props: any) => { }); const [hasAccess, setHasAccess] = useState(true); const { http, chrome, application } = coreRefs; + const [selectedDatabase, setSelectedDatabase] = useState(''); + + const { + loadStatus: databasesLoadStatus, + startLoading: startLoadingDatabases, + } = useLoadDatabasesToCache(); + const { loadStatus: tablesLoadStatus, startLoading: startLoadingTables } = useLoadTablesToCache(); + const { + loadStatus: accelerationsLoadStatus, + startLoading: startLoadingAccelerations, + } = useLoadAccelerationsToCache(); + + const cacheLoadingHooks = { + databasesLoadStatus, + startLoadingDatabases, + tablesLoadStatus, + startLoadingTables, + accelerationsLoadStatus, + startLoadingAccelerations, + }; // Dummy accelerations variables for mock purposes // Actual accelerations should be retrieved from the backend @@ -197,19 +208,22 @@ export const DataConnection = (props: any) => { id: 'associated_objects', name: 'Associated Objects', disabled: false, - content: , + content: ( + + ), }, { id: 'acceleration_table', name: 'Accelerations', disabled: false, - content: , - }, - // TODO: Installed integrations page - { - id: 'installed_integrations', - name: 'Installed Integrations', - disabled: false, + content: ( + + ), }, { id: 'access_control', @@ -357,9 +371,9 @@ export const DataConnection = (props: any) => { - ); }; +export { DatasourceDetails }; diff --git a/public/framework/catalog_cache/cache_loader.tsx b/public/framework/catalog_cache/cache_loader.tsx index 43093b665..93e07a013 100644 --- a/public/framework/catalog_cache/cache_loader.tsx +++ b/public/framework/catalog_cache/cache_loader.tsx @@ -230,7 +230,7 @@ export const useLoadToCache = (loadCacheType: LoadCacheType) => { const [currentDatabaseName, setCurrentDatabaseName] = useState(''); const [currentTableName, setCurrentTableName] = useState(''); const [loadStatus, setLoadStatus] = useState( - DirectQueryLoadingStatus.SCHEDULED + DirectQueryLoadingStatus.INITIAL ); const { diff --git a/public/framework/catalog_cache/cache_manager.test.tsx b/public/framework/catalog_cache/cache_manager.test.tsx index f73609eda..c1cabdf25 100644 --- a/public/framework/catalog_cache/cache_manager.test.tsx +++ b/public/framework/catalog_cache/cache_manager.test.tsx @@ -10,7 +10,7 @@ import { } from '../../../common/constants/shared'; import { AccelerationsCacheData, - CachedAcclerationByDataSource, + CachedAccelerationByDataSource, CachedDataSource, CachedDataSourceStatus, CachedDatabase, @@ -394,7 +394,7 @@ describe('CatalogCacheManager', () => { describe('addOrUpdateAccelerationsByDataSource', () => { it('should add a new data source to the accelerations cache', () => { - const dataSource: CachedAcclerationByDataSource = { + const dataSource: CachedAccelerationByDataSource = { name: 'TestDataSource', lastUpdated: '2024-03-08T12:00:00Z', status: CachedDataSourceStatus.Updated, @@ -415,7 +415,7 @@ describe('CatalogCacheManager', () => { it('should update an existing data source in the accelerations cache', () => { // Set up initial cache data - const initialDataSource: CachedAcclerationByDataSource = { + const initialDataSource: CachedAccelerationByDataSource = { name: 'TestDataSource', lastUpdated: '2024-03-08T12:00:00Z', status: CachedDataSourceStatus.Updated, @@ -423,7 +423,7 @@ describe('CatalogCacheManager', () => { }; // Update the data source - const updatedDataSource: CachedAcclerationByDataSource = { + const updatedDataSource: CachedAccelerationByDataSource = { ...initialDataSource, status: CachedDataSourceStatus.Failed, }; @@ -444,7 +444,7 @@ describe('CatalogCacheManager', () => { describe('getOrCreateAccelerationsByDataSource', () => { it('should return an existing data source from the accelerations cache', () => { // Set up initial cache data - const existingDataSource: CachedAcclerationByDataSource = { + const existingDataSource: CachedAccelerationByDataSource = { name: 'TestDataSource', lastUpdated: '2024-03-08T12:00:00Z', status: CachedDataSourceStatus.Updated, diff --git a/public/framework/catalog_cache/cache_manager.ts b/public/framework/catalog_cache/cache_manager.ts index 225cd8a9b..a5a564ad3 100644 --- a/public/framework/catalog_cache/cache_manager.ts +++ b/public/framework/catalog_cache/cache_manager.ts @@ -10,7 +10,7 @@ import { } from '../../../common/constants/shared'; import { AccelerationsCacheData, - CachedAcclerationByDataSource, + CachedAccelerationByDataSource, CachedDataSource, CachedDataSourceStatus, CachedDatabase, @@ -85,12 +85,12 @@ export class CatalogCacheManager { /** * Adds or updates a data source in the accelerations cache. - * @param {CachedAcclerationByDataSource} dataSource - The data source to add or update. + * @param {CachedAccelerationByDataSource} dataSource - The data source to add or update. */ - static addOrUpdateAccelerationsByDataSource(dataSource: CachedAcclerationByDataSource): void { + static addOrUpdateAccelerationsByDataSource(dataSource: CachedAccelerationByDataSource): void { const accCacheData = this.getAccelerationsCache(); const index = accCacheData.dataSources.findIndex( - (ds: CachedAcclerationByDataSource) => ds.name === dataSource.name + (ds: CachedAccelerationByDataSource) => ds.name === dataSource.name ); if (index !== -1) { accCacheData.dataSources[index] = dataSource; @@ -103,12 +103,12 @@ export class CatalogCacheManager { /** * Retrieves accelerations cache from local storage by the datasource name. * @param {string} dataSourceName - The name of the data source. - * @returns {CachedAcclerationByDataSource} The retrieved accelerations by datasource in cache. + * @returns {CachedAccelerationByDataSource} The retrieved accelerations by datasource in cache. * @throws {Error} If the data source is not found. */ static getOrCreateAccelerationsByDataSource( dataSourceName: string - ): CachedAcclerationByDataSource { + ): CachedAccelerationByDataSource { const accCacheData = this.getAccelerationsCache(); const cachedDataSource = accCacheData.dataSources.find((ds) => ds.name === dataSourceName); diff --git a/public/plugin.tsx b/public/plugin.tsx index e28613a23..b35ffc75f 100644 --- a/public/plugin.tsx +++ b/public/plugin.tsx @@ -48,7 +48,7 @@ import { observabilityTracesTitle, } from '../common/constants/shared'; import { QueryManager } from '../common/query_manager'; -import { AssociatedObject } from '../common/types/data_connections'; +import { AssociatedObject, CachedAcceleration } from '../common/types/data_connections'; import { VISUALIZATION_SAVED_OBJECT } from '../common/types/observability_saved_object_attributes'; import { setOSDHttp, @@ -103,7 +103,17 @@ interface PublicConfig { export const [ getRenderAccelerationDetailsFlyout, setRenderAccelerationDetailsFlyout, -] = createGetterSetter<(acceleration: any) => void>('renderAccelerationDetailsFlyout'); +] = createGetterSetter< + ({ + index, + acceleration, + dataSourceName, + }: { + index: string; + acceleration: CachedAcceleration; + dataSourceName: string; + }) => void +>('renderAccelerationDetailsFlyout'); export const [ getRenderAssociatedObjectsDetailsFlyout, @@ -392,9 +402,23 @@ export class ObservabilityPlugin }); // Use overlay service to render flyouts - const renderAccelerationDetailsFlyout = (acceleration: any) => + const renderAccelerationDetailsFlyout = ({ + index, + acceleration, + dataSourceName, + }: { + index: string; + acceleration: CachedAcceleration; + dataSourceName: string; + }) => core.overlays.openFlyout( - toMountPoint() + toMountPoint( + + ) ); setRenderAccelerationDetailsFlyout(renderAccelerationDetailsFlyout); diff --git a/test/datasources.ts b/test/datasources.ts index 69b2d2c55..41ce908f1 100644 --- a/test/datasources.ts +++ b/test/datasources.ts @@ -3,7 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { AsyncPollingResult, DatasourceType } from '../common/types/data_connections'; +import { + AccelerationsCacheData, + AsyncPollingResult, + CachedDataSourceStatus, + DataSourceCacheData, + DatasourceType, + DatasourceDetails, + AssociatedObject, +} from '../common/types/data_connections'; export const showDataConnectionsData = { schema: [ @@ -46,7 +54,7 @@ export const showDataConnectionsData = { export const describePrometheusDataConnection = { name: 'prom', description: '', - connector: 'PROMETHEUS', + connector: 'mock_data_source', allowedRoles: [], properties: { 'prometheus.uri': 'localhost:9201', @@ -54,7 +62,7 @@ export const describePrometheusDataConnection = { }; export const testS3ConnectionDetails = { - dataConnection: 'ya', + dataConnection: 'mock_data_source', description: '', connector: 'S3GLUE' as DatasourceType, properties: { @@ -856,3 +864,320 @@ export const mockShowIndexesPollingResult: AsyncPollingResult = { ['flint_mys3_default_http_count_view', 'mv', 'default', '', 'http_count_view', true, 'Active'], ], }; + +export const mockDatasource: DatasourceDetails = { + allowedRoles: ['admin'], + name: 'mock_data_source', + connector: 'S3GLUE', + description: '', + properties: { + 'glue.indexstore.opensearch.uri': '', + 'glue.indexstore.opensearch.region': '', + }, +}; + +export const mockDataSourceCacheData: DataSourceCacheData = { + version: '1.0', + dataSources: [ + { + name: 'mock_data_source', + lastUpdated: '2024-03-14T12:00:00Z', + status: CachedDataSourceStatus.Updated, + databases: [ + { + name: 'mock_database_1', + lastUpdated: '2024-03-14T12:00:00Z', + status: CachedDataSourceStatus.Updated, + tables: [ + { + name: 'mock_table_1', + }, + { + name: 'mock_table_2', + }, + { + name: 'mock_table_3', + }, + { + name: 'mock_table_4', + }, + { + name: 'mock_table_5', + }, + ], + }, + ], + }, + ], +}; + +export const mockAccelerationCacheData: AccelerationsCacheData = { + version: '1.0', + dataSources: [ + { + name: 'mock_data_source', + lastUpdated: '2024-03-14T12:00:00Z', + status: CachedDataSourceStatus.Updated, + accelerations: [ + { + flintIndexName: 'mock_acceleration_1', + type: 'skipping', + database: 'mock_database_1', + table: '', + indexName: 'mock_acceleration_1', + autoRefresh: true, + status: '', + }, + { + flintIndexName: 'mock_acceleration_2', + type: 'materialized', + database: 'mock_database_1', + table: '', + indexName: 'mock_acceleration_2', + autoRefresh: true, + status: '', + }, + ], + }, + ], +}; + +export const mockEmptyDataSourceCacheData: DataSourceCacheData = { + version: '1.0', + dataSources: [ + { + name: 'mock_data_source', + lastUpdated: '2024-03-14T12:00:00Z', + status: CachedDataSourceStatus.Updated, + databases: [], + }, + ], +}; + +export const mockEmptyAccelerationCacheData: AccelerationsCacheData = { + version: '1.0', + dataSources: [ + { + name: 'mock_data_source', + lastUpdated: '2024-03-14T12:00:00Z', + status: CachedDataSourceStatus.Updated, + accelerations: [], + }, + ], +}; + +export const mockAssociatedObjects: AssociatedObject[] = [ + { + id: '1', + datasource: 'flint_s3', + name: 'Table_name_1', + database: 'db1', + type: 'Table', + createdByIntegration: 'integration_1', + accelerations: [ + { + name: 'skipping_index_1', + status: 'ACTIVE', + type: 'skip', + database: 'db1', + table: 'Table_name_1', + destination: 'N/A', + dateCreated: 1709339290, + dateUpdated: 1709339290, + index: 'security_logs_2022', + sql: 'SELECT * FROM Table_name_1 WHERE ...', + }, + ], + columns: [ + { + name: 'column1', + dataType: 'dataType1', + }, + { + name: 'column2', + dataType: 'dataType2', + }, + { + name: 'column3', + dataType: 'dataType3', + }, + { + name: 'column4', + dataType: 'dataType4', + }, + ], + }, + { + id: '2', + datasource: 'flint_s3', + name: 'Table_name_2', + database: 'db1', + type: 'Table', + createdByIntegration: 'integration_1', + accelerations: [ + { + name: 'skipping_index_2', + status: 'ACTIVE', + type: 'skip', + database: 'db1', + table: 'Table_name_1', + destination: 'N/A', + dateCreated: 1709339290, + dateUpdated: 1709339290, + index: 'security_logs_2022', + sql: 'SELECT * FROM Table_name_1 WHERE ...', + }, + ], + columns: [], + }, + { + id: '3', + datasource: 'flint_s3', + name: 'skipping_index_2', + database: 'db1', + type: 'Skip Index', + createdByIntegration: 'integration_1', + accelerations: [ + { + name: 'skipping_index_2', + status: 'ACTIVE', + type: 'skip', + database: 'db1', + table: 'Table_name_1', + destination: 'N/A', + dateCreated: 1709339290, + dateUpdated: 1709339290, + index: 'security_logs_2022', + sql: 'SELECT * FROM Table_name_1 WHERE ...', + }, + ], + columns: [], + }, + { + id: '4', + datasource: 'flint_s3', + name: 'Table_name_4', + database: 'db2', + type: 'Table', + createdByIntegration: 'integration_1', + accelerations: [ + { + name: 'skipping_index_2', + status: 'ACTIVE', + type: 'skip', + database: 'db1', + table: 'Table_name_1', + destination: 'N/A', + dateCreated: 1709339290, + dateUpdated: 1709339290, + index: 'security_logs_2022', + sql: 'SELECT * FROM Table_name_1 WHERE ...', + }, + ], + columns: [ + { + name: 'column1', + dataType: 'dataType1', + }, + { + name: 'column2', + dataType: 'dataType2', + }, + ], + }, + { + id: '5', + datasource: 'flint_s3', + name: 'Table_name_5', + database: 'db3', + type: 'Table', + createdByIntegration: 'integration_1', + accelerations: [], + columns: [ + { + name: 'column1', + dataType: 'dataType1', + }, + { + name: 'column2', + dataType: 'dataType2', + }, + ], + }, + { + id: '6', + datasource: 'flint_s3', + name: 'covering_index_3', + database: 'db3', + type: 'Cover Index', + createdByIntegration: '', + accelerations: [ + { + name: 'covering_index_3', + status: 'ACTIVE', + type: 'skip', + database: 'db1', + table: 'Table_name_1', + destination: 'N/A', + dateCreated: 1709339290, + dateUpdated: 1709339290, + index: 'security_logs_2022', + sql: 'SELECT * FROM Table_name_1 WHERE ...', + }, + ], + columns: [ + { + name: 'column1', + dataType: 'dataType1', + }, + { + name: 'column2', + dataType: 'dataType2', + }, + ], + }, + { + id: '7', + datasource: 'flint_s3', + name: 'Table_name_6', + database: 'db3', + type: 'Table', + createdByIntegration: '', + accelerations: [ + { + name: 'skipping_index_4', + status: 'ACTIVE', + type: 'skip', + database: 'db1', + table: 'Table_name_1', + destination: 'N/A', + dateCreated: 1709339290, + dateUpdated: 1709339290, + index: 'security_logs_2022', + sql: 'SELECT * FROM Table_name_1 WHERE ...', + }, + { + name: 'skipping_index_5', + status: 'ACTIVE', + type: 'skip', + database: 'db1', + table: 'Table_name_1', + destination: 'N/A', + dateCreated: 1709339290, + dateUpdated: 1709339290, + index: 'security_logs_2022', + sql: 'SELECT * FROM Table_name_1 WHERE ...', + }, + ], + columns: [ + { + name: 'column1', + dataType: 'dataType1', + }, + { + name: 'column2', + dataType: 'dataType2', + }, + ], + }, +];