From a137624a8af6de7badbf4115c4a83e7b074cbb33 Mon Sep 17 00:00:00 2001 From: tygao Date: Thu, 5 Sep 2024 17:08:11 +0800 Subject: [PATCH 01/12] feat: integrate workspace with data connections Signed-off-by: tygao --- src/plugins/data_source/common/data_sources/types.ts | 2 ++ .../components/workspace_creator/workspace_creator.tsx | 8 ++++++++ src/plugins/workspace/public/utils.ts | 5 ++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/plugins/data_source/common/data_sources/types.ts b/src/plugins/data_source/common/data_sources/types.ts index 32432def651..515650b6b5e 100644 --- a/src/plugins/data_source/common/data_sources/types.ts +++ b/src/plugins/data_source/common/data_sources/types.ts @@ -61,3 +61,5 @@ export enum DataSourceEngineType { Elasticsearch = 'Elasticsearch', NA = 'No Engine Type Available', } + +export const DATA_SOURCE_SAVED_OBJECT_TYPE = 'data-source'; diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx index ed4370a7b3f..91d234982bc 100644 --- a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx +++ b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx @@ -22,6 +22,8 @@ import { useFormAvailableUseCases } from '../workspace_form/use_form_available_u import { NavigationPublicPluginStart } from '../../../../../plugins/navigation/public'; import { DataSourceConnectionType } from '../../../common/types'; import { WorkspaceCreatorForm } from './workspace_creator_form'; +import { DATA_CONNECTION_SAVED_OBJECT_TYPE } from '../../../../../plugins/data_source/common/data_connections'; +import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../../../../plugins/data_source/common/data_sources'; export interface WorkspaceCreatorProps { registeredUseCases$: BehaviorSubject; @@ -80,8 +82,14 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => { .map(({ id }) => { return id; }); + const selectedDataConnectionIds = (selectedDataSourceConnections ?? []) + .filter(({ connectionType }) => connectionType === DATA_CONNECTION_SAVED_OBJECT_TYPE) + .map(({ id }) => { + return id; + }); result = await workspaceClient.create(attributes, { dataSources: selectedDataSourceIds, + dataConnections: selectedDataConnectionIds, permissions: convertPermissionSettingsToPermissions(permissionSettings), }); if (result?.success) { diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index 49132b64f46..e9ebda57412 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -44,6 +44,7 @@ import { SEARCH_OVERVIEW_PAGE_ID, SECURITY_ANALYTICS_OVERVIEW_PAGE_ID, } from '../../../plugins/content_management/public'; +import { WORKSPACE_DATA_SOURCE_AND_CONNECTION_OBJECT_TYPES } from '../common/constants'; export const isUseCaseFeatureConfig = (featureConfig: string) => featureConfig.startsWith(USE_CASE_PREFIX); @@ -220,7 +221,7 @@ export const getDataSourcesList = ( ) => { return client .find({ - type: 'data-source', + type: WORKSPACE_DATA_SOURCE_AND_CONNECTION_OBJECT_TYPES, fields: ['id', 'title', 'auth', 'description', 'dataSourceEngineType'], perPage: 10000, workspaces: targetWorkspaces, @@ -235,6 +236,7 @@ export const getDataSourcesList = ( const auth = source.get('auth'); const description = source.get('description'); const dataSourceEngineType = source.get('dataSourceEngineType'); + const type = source.type; return { id, title, @@ -242,6 +244,7 @@ export const getDataSourcesList = ( description, dataSourceEngineType, workspaces, + type, }; }); } else { From 4771d3729f6ccbc091cde7f005ff2cedd2701c80 Mon Sep 17 00:00:00 2001 From: tygao Date: Fri, 13 Sep 2024 18:31:20 +0800 Subject: [PATCH 02/12] update workspace pages and hooks to integrate with data connection Signed-off-by: tygao --- .../workspace_creator/workspace_creator.tsx | 2 +- .../association_data_source_modal.tsx | 14 +++++++ .../workspace_detail_connection_table.tsx | 5 ++- .../workspace_form/data_connection_icon.tsx | 14 +++++++ .../public/components/workspace_form/index.ts | 1 + src/plugins/workspace/public/utils.ts | 42 ++++++++++++------- 6 files changed, 59 insertions(+), 19 deletions(-) create mode 100644 src/plugins/workspace/public/components/workspace_form/data_connection_icon.tsx diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx index 91d234982bc..a59647782fd 100644 --- a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx +++ b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx @@ -83,7 +83,7 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => { return id; }); const selectedDataConnectionIds = (selectedDataSourceConnections ?? []) - .filter(({ connectionType }) => connectionType === DATA_CONNECTION_SAVED_OBJECT_TYPE) + .filter(({ type }) => type === DATA_CONNECTION_SAVED_OBJECT_TYPE) .map(({ id }) => { return id; }); diff --git a/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.tsx b/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.tsx index f573a46487d..978a5e473cc 100644 --- a/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.tsx +++ b/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.tsx @@ -31,6 +31,9 @@ import { HttpStart, NotificationsStart, SavedObjectsStart } from '../../../../.. import { AssociationDataSourceModalMode } from '../../../common/constants'; import { Logos } from '../../../../../core/common'; import { DirectQueryConnectionIcon } from '../workspace_form'; +import { DataConnectionIcon } from '../workspace_form'; + +import { DATA_CONNECTION_SAVED_OBJECT_TYPE } from '../../../../data_source/common/data_connections'; const ConnectionIcon = ({ connection: { connectionType, type }, @@ -45,6 +48,9 @@ const ConnectionIcon = ({ if (connectionType === DataSourceConnectionType.DirectQueryConnection) { return ; } + if (type === DATA_CONNECTION_SAVED_OBJECT_TYPE) { + return ; + } return null; }; @@ -143,6 +149,14 @@ const convertConnectionsToOptions = ({ ) { return []; } + + if (connection.type === DATA_CONNECTION_SAVED_OBJECT_TYPE) { + if (showDirectQueryConnections) { + return [connection]; + } + return []; + } + if (showDirectQueryConnections) { if (!connection.relatedConnections || connection.relatedConnections.length === 0) { return []; diff --git a/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx b/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx index 48ff7ee4408..79090f91e88 100644 --- a/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx +++ b/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx @@ -9,7 +9,7 @@ import { i18n } from '@osd/i18n'; import { DataSourceConnection, DataSourceConnectionType } from '../../../common/types'; import { AssociationDataSourceModalMode } from '../../../common/constants'; import { DataSourceConnectionTable } from '../workspace_form'; - +import { DATA_CONNECTION_SAVED_OBJECT_TYPE } from '../../../../data_source/common/data_connections'; interface WorkspaceDetailConnectionTableProps { isDashboardAdmin: boolean; connectionType: string; @@ -36,7 +36,8 @@ export const WorkspaceDetailConnectionTable = ({ return dataSourceConnections.filter((dsc) => connectionType === AssociationDataSourceModalMode.OpenSearchConnections ? dsc.connectionType === DataSourceConnectionType.OpenSearchConnection - : dsc?.relatedConnections && dsc.relatedConnections?.length > 0 + : dsc.type === DATA_CONNECTION_SAVED_OBJECT_TYPE || + (dsc?.relatedConnections && dsc.relatedConnections?.length > 0) ); }, [connectionType, dataSourceConnections]); diff --git a/src/plugins/workspace/public/components/workspace_form/data_connection_icon.tsx b/src/plugins/workspace/public/components/workspace_form/data_connection_icon.tsx new file mode 100644 index 00000000000..e01df03564b --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_form/data_connection_icon.tsx @@ -0,0 +1,14 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiIcon } from '@elastic/eui'; + +export const DataConnectionIcon = ({ type }: { type?: string }) => { + switch (type) { + default: + return ; + } +}; diff --git a/src/plugins/workspace/public/components/workspace_form/index.ts b/src/plugins/workspace/public/components/workspace_form/index.ts index 2fae243a37a..6fec031fc03 100644 --- a/src/plugins/workspace/public/components/workspace_form/index.ts +++ b/src/plugins/workspace/public/components/workspace_form/index.ts @@ -11,6 +11,7 @@ export { WorkspacePermissionSettingPanel } from './workspace_permission_setting_ export { WorkspaceCancelModal } from './workspace_cancel_modal'; export { WorkspaceNameField, WorkspaceDescriptionField } from './fields'; export { DirectQueryConnectionIcon } from './direct_query_connection_icon'; +export { DataConnectionIcon } from './data_connection_icon'; export { DataSourceConnectionTable } from './data_source_connection_table'; export { WorkspaceFormSubmitData, WorkspaceFormProps, WorkspaceFormDataState } from './types'; diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index e9ebda57412..5ddeb6e406c 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -45,6 +45,8 @@ import { SECURITY_ANALYTICS_OVERVIEW_PAGE_ID, } from '../../../plugins/content_management/public'; import { WORKSPACE_DATA_SOURCE_AND_CONNECTION_OBJECT_TYPES } from '../common/constants'; +import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../data_source/common/data_sources'; +import { DATA_CONNECTION_SAVED_OBJECT_TYPE } from '../../data_source/common/data_connections'; export const isUseCaseFeatureConfig = (featureConfig: string) => featureConfig.startsWith(USE_CASE_PREFIX); @@ -222,6 +224,7 @@ export const getDataSourcesList = ( return client .find({ type: WORKSPACE_DATA_SOURCE_AND_CONNECTION_OBJECT_TYPES, + // type: 'data-source', fields: ['id', 'title', 'auth', 'description', 'dataSourceEngineType'], perPage: 10000, workspaces: targetWorkspaces, @@ -272,16 +275,18 @@ export const getDirectQueryConnections = async (dataSourceId: string, http: Http export const convertDataSourcesToOpenSearchConnections = ( dataSources: DataSource[] ): DataSourceConnection[] => - dataSources.map((ds) => { - return { - id: ds.id, - type: ds.dataSourceEngineType, - connectionType: DataSourceConnectionType.OpenSearchConnection, - name: ds.title, - description: ds.description, - relatedConnections: [], - }; - }); + dataSources + .filter((ds) => ds.type === DATA_SOURCE_SAVED_OBJECT_TYPE) + .map((ds) => { + return { + id: ds.id, + type: ds.dataSourceEngineType, + connectionType: DataSourceConnectionType.OpenSearchConnection, + name: ds.title, + description: ds.description, + relatedConnections: [], + }; + }); export const fulfillRelatedConnections = ( connections: DataSourceConnection[], @@ -304,11 +309,16 @@ export const mergeDataSourcesWithConnections = ( directQueryConnections: DataSourceConnection[] ): DataSourceConnection[] => { const openSearchConnections = convertDataSourcesToOpenSearchConnections(dataSources); - - return [ + const dataConnections = dataSources + .filter((ds) => ds.type === DATA_CONNECTION_SAVED_OBJECT_TYPE) + .map((ds) => ({ ...ds, name: ds.id })); + const result = [ ...fulfillRelatedConnections(openSearchConnections, directQueryConnections), ...directQueryConnections, - ].sort((a, b) => a.name.localeCompare(b.name)); + ...dataConnections, + ].sort((a, b) => a?.name?.localeCompare(b?.name)); + + return result; }; // If all connected data sources are serverless, will only allow to select essential use case. @@ -508,16 +518,16 @@ export const fetchDataSourceConnectionsByDataSourceIds = async ( }; export const fetchDataSourceConnections = async ( - assignedDataSources: DataSource[], + dataSources: DataSource[], http: HttpSetup | undefined, notifications: NotificationsStart | undefined ) => { try { const directQueryConnections = await fetchDataSourceConnectionsByDataSourceIds( - assignedDataSources.map((ds) => ds.id), + dataSources.filter((ds) => ds.type === DATA_SOURCE_SAVED_OBJECT_TYPE).map((ds) => ds.id), http ); - return mergeDataSourcesWithConnections(assignedDataSources, directQueryConnections); + return mergeDataSourcesWithConnections(dataSources, directQueryConnections); } catch (error) { notifications?.toasts.addDanger( i18n.translate('workspace.detail.dataSources.error.message', { From 5136db12dd22fc515c50cc090407f3a3853258bd Mon Sep 17 00:00:00 2001 From: "opensearch-changeset-bot[bot]" <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 10:33:20 +0000 Subject: [PATCH 03/12] Changeset file for PR #8013 created/updated --- changelogs/fragments/8013.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelogs/fragments/8013.yml diff --git a/changelogs/fragments/8013.yml b/changelogs/fragments/8013.yml new file mode 100644 index 00000000000..08aa794b51a --- /dev/null +++ b/changelogs/fragments/8013.yml @@ -0,0 +1,2 @@ +feat: +- Integrate workspace with data connections ([#8013](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8013)) \ No newline at end of file From 7af04caa5a3b56ab2deb8d6e978216cff544888a Mon Sep 17 00:00:00 2001 From: tygao Date: Fri, 13 Sep 2024 18:37:51 +0800 Subject: [PATCH 04/12] remove extra comments Signed-off-by: tygao --- src/plugins/workspace/public/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index 5ddeb6e406c..52adb297d2b 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -224,7 +224,6 @@ export const getDataSourcesList = ( return client .find({ type: WORKSPACE_DATA_SOURCE_AND_CONNECTION_OBJECT_TYPES, - // type: 'data-source', fields: ['id', 'title', 'auth', 'description', 'dataSourceEngineType'], perPage: 10000, workspaces: targetWorkspaces, @@ -524,6 +523,7 @@ export const fetchDataSourceConnections = async ( ) => { try { const directQueryConnections = await fetchDataSourceConnectionsByDataSourceIds( + // Only data source saved object type needs to fetch data source connections, data connection type object not. dataSources.filter((ds) => ds.type === DATA_SOURCE_SAVED_OBJECT_TYPE).map((ds) => ds.id), http ); From 2e303f16f07654b375f977a7d507723cba849dd0 Mon Sep 17 00:00:00 2001 From: tygao Date: Sat, 14 Sep 2024 12:35:41 +0800 Subject: [PATCH 05/12] update data source import Signed-off-by: tygao --- src/plugins/data_source/common/data_sources/types.ts | 2 -- .../components/workspace_creator/workspace_creator.tsx | 6 ++++-- .../workspace_detail/association_data_source_modal.tsx | 2 +- .../workspace_detail/workspace_detail_connection_table.tsx | 2 +- src/plugins/workspace/public/utils.ts | 6 ++++-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/plugins/data_source/common/data_sources/types.ts b/src/plugins/data_source/common/data_sources/types.ts index 515650b6b5e..32432def651 100644 --- a/src/plugins/data_source/common/data_sources/types.ts +++ b/src/plugins/data_source/common/data_sources/types.ts @@ -61,5 +61,3 @@ export enum DataSourceEngineType { Elasticsearch = 'Elasticsearch', NA = 'No Engine Type Available', } - -export const DATA_SOURCE_SAVED_OBJECT_TYPE = 'data-source'; diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx index a59647782fd..212ba6dcb05 100644 --- a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx +++ b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx @@ -22,8 +22,10 @@ import { useFormAvailableUseCases } from '../workspace_form/use_form_available_u import { NavigationPublicPluginStart } from '../../../../../plugins/navigation/public'; import { DataSourceConnectionType } from '../../../common/types'; import { WorkspaceCreatorForm } from './workspace_creator_form'; -import { DATA_CONNECTION_SAVED_OBJECT_TYPE } from '../../../../../plugins/data_source/common/data_connections'; -import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../../../../plugins/data_source/common/data_sources'; +import { + DATA_CONNECTION_SAVED_OBJECT_TYPE, + DATA_SOURCE_SAVED_OBJECT_TYPE, +} from '../../../../../plugins/data_source/common'; export interface WorkspaceCreatorProps { registeredUseCases$: BehaviorSubject; diff --git a/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.tsx b/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.tsx index 978a5e473cc..230accd2760 100644 --- a/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.tsx +++ b/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.tsx @@ -33,7 +33,7 @@ import { Logos } from '../../../../../core/common'; import { DirectQueryConnectionIcon } from '../workspace_form'; import { DataConnectionIcon } from '../workspace_form'; -import { DATA_CONNECTION_SAVED_OBJECT_TYPE } from '../../../../data_source/common/data_connections'; +import { DATA_CONNECTION_SAVED_OBJECT_TYPE } from '../../../../data_source/common'; const ConnectionIcon = ({ connection: { connectionType, type }, diff --git a/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx b/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx index 79090f91e88..d4006fb268c 100644 --- a/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx +++ b/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx @@ -9,7 +9,7 @@ import { i18n } from '@osd/i18n'; import { DataSourceConnection, DataSourceConnectionType } from '../../../common/types'; import { AssociationDataSourceModalMode } from '../../../common/constants'; import { DataSourceConnectionTable } from '../workspace_form'; -import { DATA_CONNECTION_SAVED_OBJECT_TYPE } from '../../../../data_source/common/data_connections'; +import { DATA_CONNECTION_SAVED_OBJECT_TYPE } from '../../../../data_source/common'; interface WorkspaceDetailConnectionTableProps { isDashboardAdmin: boolean; connectionType: string; diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index 52adb297d2b..46d0a5b1e55 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -45,8 +45,10 @@ import { SECURITY_ANALYTICS_OVERVIEW_PAGE_ID, } from '../../../plugins/content_management/public'; import { WORKSPACE_DATA_SOURCE_AND_CONNECTION_OBJECT_TYPES } from '../common/constants'; -import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../data_source/common/data_sources'; -import { DATA_CONNECTION_SAVED_OBJECT_TYPE } from '../../data_source/common/data_connections'; +import { + DATA_SOURCE_SAVED_OBJECT_TYPE, + DATA_CONNECTION_SAVED_OBJECT_TYPE, +} from '../../data_source/common'; export const isUseCaseFeatureConfig = (featureConfig: string) => featureConfig.startsWith(USE_CASE_PREFIX); From c628a36028a85f1ac6d6921c10205ae29a62a5bc Mon Sep 17 00:00:00 2001 From: tygao Date: Sat, 14 Sep 2024 13:28:37 +0800 Subject: [PATCH 06/12] test: update tests Signed-off-by: tygao --- src/plugins/workspace/public/utils.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/workspace/public/utils.test.ts b/src/plugins/workspace/public/utils.test.ts index 5f6b28ee59e..a1ea31aae11 100644 --- a/src/plugins/workspace/public/utils.test.ts +++ b/src/plugins/workspace/public/utils.test.ts @@ -21,6 +21,7 @@ import { coreMock } from '../../../core/public/mocks'; import { WORKSPACE_DETAIL_APP_ID, USE_CASE_PREFIX } from '../common/constants'; import { SigV4ServiceName } from '../../../plugins/data_source/common/data_sources'; import { createMockedRegisteredUseCases } from './mocks'; +import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../data_source/common'; const startMock = coreMock.createStart(); const STATIC_USE_CASES = createMockedRegisteredUseCases(); @@ -375,6 +376,7 @@ describe('workspace utils: getDataSourcesList', () => { savedObjects: [ { id: 'id1', + type: DATA_SOURCE_SAVED_OBJECT_TYPE, get: (param: string) => { switch (param) { case 'title': @@ -394,6 +396,7 @@ describe('workspace utils: getDataSourcesList', () => { { id: 'id1', title: 'title1', + type: DATA_SOURCE_SAVED_OBJECT_TYPE, auth: 'mock_value', description: 'description1', dataSourceEngineType: 'dataSourceEngineType1', From 78629d7aa39ccbf639715fe161a2ec643b418a6d Mon Sep 17 00:00:00 2001 From: "opensearch-changeset-bot[bot]" <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 07:55:40 +0000 Subject: [PATCH 07/12] Changeset file for PR #8013 created/updated --- changelogs/fragments/8013.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/fragments/8013.yml b/changelogs/fragments/8013.yml index 08aa794b51a..607966b614f 100644 --- a/changelogs/fragments/8013.yml +++ b/changelogs/fragments/8013.yml @@ -1,2 +1,2 @@ feat: -- Integrate workspace with data connections ([#8013](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8013)) \ No newline at end of file +- Integrate workspace with data connections in front end ([#8013](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8013)) \ No newline at end of file From e4d191616c059e599ccae625109bcebc5c26c042 Mon Sep 17 00:00:00 2001 From: tygao Date: Wed, 18 Sep 2024 16:06:31 +0800 Subject: [PATCH 08/12] unify connection type icon and connectionType Signed-off-by: tygao --- src/plugins/data_source/common/index.ts | 2 +- src/plugins/workspace/common/types.ts | 10 ++++ .../public/assets/cloudwatch_logo.svg | 9 ++++ .../public/assets/security_lake_logo.svg | 9 ++++ .../workspace_creator.test.tsx | 3 ++ .../workspace_creator/workspace_creator.tsx | 8 ++- .../association_data_source_modal.tsx | 18 +++---- .../workspace_detail_connection_table.tsx | 3 +- .../workspace_form/connection_type_icon.tsx | 29 +++++++++++ .../workspace_form/data_connection_icon.tsx | 14 ------ .../data_source_connection_table.tsx | 6 +-- .../direct_query_connection_icon.tsx | 21 -------- .../public/components/workspace_form/index.ts | 3 +- src/plugins/workspace/public/utils.test.ts | 3 ++ src/plugins/workspace/public/utils.ts | 50 +++++++++++++++---- 15 files changed, 121 insertions(+), 67 deletions(-) create mode 100644 src/plugins/workspace/public/assets/cloudwatch_logo.svg create mode 100644 src/plugins/workspace/public/assets/security_lake_logo.svg create mode 100644 src/plugins/workspace/public/components/workspace_form/connection_type_icon.tsx delete mode 100644 src/plugins/workspace/public/components/workspace_form/data_connection_icon.tsx delete mode 100644 src/plugins/workspace/public/components/workspace_form/direct_query_connection_icon.tsx diff --git a/src/plugins/data_source/common/index.ts b/src/plugins/data_source/common/index.ts index 4a35e5a483f..856ca5b7d80 100644 --- a/src/plugins/data_source/common/index.ts +++ b/src/plugins/data_source/common/index.ts @@ -6,4 +6,4 @@ export const PLUGIN_ID = 'dataSource'; export const PLUGIN_NAME = 'data_source'; export const DATA_SOURCE_SAVED_OBJECT_TYPE = 'data-source'; -export { DATA_CONNECTION_SAVED_OBJECT_TYPE } from './data_connections'; +export { DATA_CONNECTION_SAVED_OBJECT_TYPE, DataConnectionType } from './data_connections'; diff --git a/src/plugins/workspace/common/types.ts b/src/plugins/workspace/common/types.ts index 50b9e838038..220192029c3 100644 --- a/src/plugins/workspace/common/types.ts +++ b/src/plugins/workspace/common/types.ts @@ -4,6 +4,8 @@ */ import { DataSourceAttributes } from 'src/plugins/data_source/common/data_sources'; +import { DataConnectionSavedObjectAttributes } from 'src/plugins/data_source/common/data_connections'; +import { DataConnectionType } from '../../data_source/common'; export type DataSource = Pick< DataSourceAttributes, @@ -13,6 +15,14 @@ export type DataSource = Pick< id: string; }; +export type DataConnection = Pick & { + type: string; + id: string; + connectionType: DataConnectionType; + description?: string; + title: string; +}; + export enum DataSourceConnectionType { OpenSearchConnection, DirectQueryConnection, diff --git a/src/plugins/workspace/public/assets/cloudwatch_logo.svg b/src/plugins/workspace/public/assets/cloudwatch_logo.svg new file mode 100644 index 00000000000..4331ae3eb4f --- /dev/null +++ b/src/plugins/workspace/public/assets/cloudwatch_logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/plugins/workspace/public/assets/security_lake_logo.svg b/src/plugins/workspace/public/assets/security_lake_logo.svg new file mode 100644 index 00000000000..47e1152a6e7 --- /dev/null +++ b/src/plugins/workspace/public/assets/security_lake_logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.test.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.test.tsx index 4efbc30680c..241220ee685 100644 --- a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.test.tsx +++ b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.test.tsx @@ -243,6 +243,7 @@ describe('WorkspaceCreator', () => { }), { dataSources: [], + dataConnections: [], permissions: { library_write: { users: ['%me%'] }, write: { users: ['%me%'] }, @@ -318,6 +319,7 @@ describe('WorkspaceCreator', () => { name: 'test workspace name', }), { + dataConnections: [], dataSources: [], permissions: { write: { @@ -375,6 +377,7 @@ describe('WorkspaceCreator', () => { name: 'test workspace name', }), { + dataConnections: [], dataSources: ['id1'], permissions: { library_write: { diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx index 212ba6dcb05..13b868c164c 100644 --- a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx +++ b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx @@ -22,10 +22,6 @@ import { useFormAvailableUseCases } from '../workspace_form/use_form_available_u import { NavigationPublicPluginStart } from '../../../../../plugins/navigation/public'; import { DataSourceConnectionType } from '../../../common/types'; import { WorkspaceCreatorForm } from './workspace_creator_form'; -import { - DATA_CONNECTION_SAVED_OBJECT_TYPE, - DATA_SOURCE_SAVED_OBJECT_TYPE, -} from '../../../../../plugins/data_source/common'; export interface WorkspaceCreatorProps { registeredUseCases$: BehaviorSubject; @@ -85,7 +81,9 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => { return id; }); const selectedDataConnectionIds = (selectedDataSourceConnections ?? []) - .filter(({ type }) => type === DATA_CONNECTION_SAVED_OBJECT_TYPE) + .filter( + ({ connectionType }) => connectionType === DataSourceConnectionType.DataConnection + ) .map(({ id }) => { return id; }); diff --git a/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.tsx b/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.tsx index 230accd2760..eacef4f0deb 100644 --- a/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.tsx +++ b/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.tsx @@ -30,10 +30,7 @@ import { DataSourceConnection, DataSourceConnectionType } from '../../../common/ import { HttpStart, NotificationsStart, SavedObjectsStart } from '../../../../../core/public'; import { AssociationDataSourceModalMode } from '../../../common/constants'; import { Logos } from '../../../../../core/common'; -import { DirectQueryConnectionIcon } from '../workspace_form'; -import { DataConnectionIcon } from '../workspace_form'; - -import { DATA_CONNECTION_SAVED_OBJECT_TYPE } from '../../../../data_source/common'; +import { ConnectionTypeIcon } from '../workspace_form'; const ConnectionIcon = ({ connection: { connectionType, type }, @@ -45,12 +42,13 @@ const ConnectionIcon = ({ if (connectionType === DataSourceConnectionType.OpenSearchConnection) { return ; } - if (connectionType === DataSourceConnectionType.DirectQueryConnection) { - return ; - } - if (type === DATA_CONNECTION_SAVED_OBJECT_TYPE) { - return ; + if ( + connectionType === DataSourceConnectionType.DirectQueryConnection || + connectionType === DataSourceConnectionType.DataConnection + ) { + return ; } + return null; }; @@ -150,7 +148,7 @@ const convertConnectionsToOptions = ({ return []; } - if (connection.type === DATA_CONNECTION_SAVED_OBJECT_TYPE) { + if (connection.connectionType === DataSourceConnectionType.DataConnection) { if (showDirectQueryConnections) { return [connection]; } diff --git a/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx b/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx index d4006fb268c..bd0fd321e22 100644 --- a/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx +++ b/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx @@ -9,7 +9,6 @@ import { i18n } from '@osd/i18n'; import { DataSourceConnection, DataSourceConnectionType } from '../../../common/types'; import { AssociationDataSourceModalMode } from '../../../common/constants'; import { DataSourceConnectionTable } from '../workspace_form'; -import { DATA_CONNECTION_SAVED_OBJECT_TYPE } from '../../../../data_source/common'; interface WorkspaceDetailConnectionTableProps { isDashboardAdmin: boolean; connectionType: string; @@ -36,7 +35,7 @@ export const WorkspaceDetailConnectionTable = ({ return dataSourceConnections.filter((dsc) => connectionType === AssociationDataSourceModalMode.OpenSearchConnections ? dsc.connectionType === DataSourceConnectionType.OpenSearchConnection - : dsc.type === DATA_CONNECTION_SAVED_OBJECT_TYPE || + : dsc.connectionType === DataSourceConnectionType.DataConnection || (dsc?.relatedConnections && dsc.relatedConnections?.length > 0) ); }, [connectionType, dataSourceConnections]); diff --git a/src/plugins/workspace/public/components/workspace_form/connection_type_icon.tsx b/src/plugins/workspace/public/components/workspace_form/connection_type_icon.tsx new file mode 100644 index 00000000000..1d3b275453b --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_form/connection_type_icon.tsx @@ -0,0 +1,29 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiIcon } from '@elastic/eui'; + +import prometheusLogo from '../../assets/prometheus_logo.svg'; +import s3Logo from '../../assets/s3_logo.svg'; +import cloudWatchLogo from '../../assets/cloudwatch_logo.svg'; +import securityLakeLogo from '../../assets/security_lake_logo.svg'; +import { DataConnectionType } from '../../../../data_source/common/'; + +// Direct query connection and data connection both have different types, each type has a corresponding icon +export const ConnectionTypeIcon = ({ type }: { type?: string }) => { + switch (type) { + case 'Amazon S3': + return ; + case 'Prometheus': + return ; + case DataConnectionType.CloudWatch: + return ; + case DataConnectionType.SecurityLake: + return ; + default: + return null; + } +}; diff --git a/src/plugins/workspace/public/components/workspace_form/data_connection_icon.tsx b/src/plugins/workspace/public/components/workspace_form/data_connection_icon.tsx deleted file mode 100644 index e01df03564b..00000000000 --- a/src/plugins/workspace/public/components/workspace_form/data_connection_icon.tsx +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { EuiIcon } from '@elastic/eui'; - -export const DataConnectionIcon = ({ type }: { type?: string }) => { - switch (type) { - default: - return ; - } -}; diff --git a/src/plugins/workspace/public/components/workspace_form/data_source_connection_table.tsx b/src/plugins/workspace/public/components/workspace_form/data_source_connection_table.tsx index 395b349a5cc..57b3ba666cb 100644 --- a/src/plugins/workspace/public/components/workspace_form/data_source_connection_table.tsx +++ b/src/plugins/workspace/public/components/workspace_form/data_source_connection_table.tsx @@ -22,7 +22,7 @@ import { import { i18n } from '@osd/i18n'; import { DataSourceConnection, DataSourceConnectionType } from '../../../common/types'; import { AssociationDataSourceModalMode } from '../../../common/constants'; -import { DirectQueryConnectionIcon } from './direct_query_connection_icon'; +import { ConnectionTypeIcon } from './connection_type_icon'; import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; import { CoreStart } from '../../../../../core/public'; @@ -167,7 +167,7 @@ export const DataSourceConnectionTable = forwardRef< togglePopover(record.id)} > @@ -191,7 +191,7 @@ export const DataSourceConnectionTable = forwardRef< key={item.id} size="xs" label={item.name} - icon={} + icon={} style={{ maxHeight: '30px' }} /> ))} diff --git a/src/plugins/workspace/public/components/workspace_form/direct_query_connection_icon.tsx b/src/plugins/workspace/public/components/workspace_form/direct_query_connection_icon.tsx deleted file mode 100644 index 34dd2bbde93..00000000000 --- a/src/plugins/workspace/public/components/workspace_form/direct_query_connection_icon.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { EuiIcon } from '@elastic/eui'; - -import prometheusLogo from '../../assets/prometheus_logo.svg'; -import s3Logo from '../../assets/s3_logo.svg'; - -export const DirectQueryConnectionIcon = ({ type }: { type?: string }) => { - switch (type) { - case 'Amazon S3': - return ; - case 'Prometheus': - return ; - default: - return null; - } -}; diff --git a/src/plugins/workspace/public/components/workspace_form/index.ts b/src/plugins/workspace/public/components/workspace_form/index.ts index 6fec031fc03..225d17bfe54 100644 --- a/src/plugins/workspace/public/components/workspace_form/index.ts +++ b/src/plugins/workspace/public/components/workspace_form/index.ts @@ -10,8 +10,7 @@ export { WorkspaceUseCase } from './workspace_use_case'; export { WorkspacePermissionSettingPanel } from './workspace_permission_setting_panel'; export { WorkspaceCancelModal } from './workspace_cancel_modal'; export { WorkspaceNameField, WorkspaceDescriptionField } from './fields'; -export { DirectQueryConnectionIcon } from './direct_query_connection_icon'; -export { DataConnectionIcon } from './data_connection_icon'; +export { ConnectionTypeIcon } from './connection_type_icon'; export { DataSourceConnectionTable } from './data_source_connection_table'; export { WorkspaceFormSubmitData, WorkspaceFormProps, WorkspaceFormDataState } from './types'; diff --git a/src/plugins/workspace/public/utils.test.ts b/src/plugins/workspace/public/utils.test.ts index a1ea31aae11..c86485f6a03 100644 --- a/src/plugins/workspace/public/utils.test.ts +++ b/src/plugins/workspace/public/utils.test.ts @@ -385,6 +385,8 @@ describe('workspace utils: getDataSourcesList', () => { return 'description1'; case 'dataSourceEngineType': return 'dataSourceEngineType1'; + case 'type': + return 'connectionType1'; case 'auth': return 'mock_value'; } @@ -400,6 +402,7 @@ describe('workspace utils: getDataSourcesList', () => { auth: 'mock_value', description: 'description1', dataSourceEngineType: 'dataSourceEngineType1', + connectionType: 'connectionType1', workspaces: [], }, ]); diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index 46d0a5b1e55..111f5287fc3 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -36,7 +36,12 @@ import { DATACONNECTIONS_BASE, DatasourceTypeToDisplayName, } from '../../data_source_management/public'; -import { DataSource, DataSourceConnection, DataSourceConnectionType } from '../common/types'; +import { + DataSource, + DataSourceConnection, + DataSourceConnectionType, + DataConnection, +} from '../common/types'; import { ANALYTICS_ALL_OVERVIEW_PAGE_ID, ESSENTIAL_OVERVIEW_PAGE_ID, @@ -226,7 +231,15 @@ export const getDataSourcesList = ( return client .find({ type: WORKSPACE_DATA_SOURCE_AND_CONNECTION_OBJECT_TYPES, - fields: ['id', 'title', 'auth', 'description', 'dataSourceEngineType'], + fields: [ + 'id', + 'title', + 'auth', + 'description', + 'dataSourceEngineType', + 'type', + 'connectionId', + ], perPage: 10000, workspaces: targetWorkspaces, }) @@ -235,12 +248,15 @@ export const getDataSourcesList = ( if (objects) { return objects.map((source) => { const id = source.id; - const title = source.get('title'); + // Data connection doesn't have title for now, would use connectionId instead to display. + const title = source.get('title') ?? source.get('connectionId') ?? ''; const workspaces = source.workspaces ?? []; const auth = source.get('auth'); const description = source.get('description'); const dataSourceEngineType = source.get('dataSourceEngineType'); const type = source.type; + // This is a filed only for detail type of data connection in order not to mix saved object type. + const connectionType = source.get('type'); return { id, title, @@ -249,6 +265,7 @@ export const getDataSourcesList = ( dataSourceEngineType, workspaces, type, + connectionType, }; }); } else { @@ -274,11 +291,11 @@ export const getDirectQueryConnections = async (dataSourceId: string, http: Http }; export const convertDataSourcesToOpenSearchConnections = ( - dataSources: DataSource[] + dataSources: DataConnection[] | DataSource[] ): DataSourceConnection[] => dataSources .filter((ds) => ds.type === DATA_SOURCE_SAVED_OBJECT_TYPE) - .map((ds) => { + .map((ds: DataSource) => { return { id: ds.id, type: ds.dataSourceEngineType, @@ -289,6 +306,23 @@ export const convertDataSourcesToOpenSearchConnections = ( }; }); +export const convertDataSourcesToDataConnections = ( + dataSources: DataConnection[] | DataSource[] +): DataSourceConnection[] => { + const dataConnections = dataSources.filter( + (ds) => ds.type === DATA_CONNECTION_SAVED_OBJECT_TYPE + ) as DataConnection[]; + return dataConnections.map((ds) => { + return { + id: ds.id, + type: ds.connectionType, + connectionType: DataSourceConnectionType.DataConnection, + name: ds.title, + description: ds.description, + }; + }); +}; + export const fulfillRelatedConnections = ( connections: DataSourceConnection[], directQueryConnections: DataSourceConnection[] @@ -306,13 +340,11 @@ export const fulfillRelatedConnections = ( // Helper function to merge data sources with direct query connections export const mergeDataSourcesWithConnections = ( - dataSources: DataSource[], + dataSources: DataSource[] | DataConnection[], directQueryConnections: DataSourceConnection[] ): DataSourceConnection[] => { const openSearchConnections = convertDataSourcesToOpenSearchConnections(dataSources); - const dataConnections = dataSources - .filter((ds) => ds.type === DATA_CONNECTION_SAVED_OBJECT_TYPE) - .map((ds) => ({ ...ds, name: ds.id })); + const dataConnections = convertDataSourcesToDataConnections(dataSources); const result = [ ...fulfillRelatedConnections(openSearchConnections, directQueryConnections), ...directQueryConnections, From c4ae0014cbcce0d70f0bb89c8c5de7110c0b59ee Mon Sep 17 00:00:00 2001 From: tygao Date: Wed, 18 Sep 2024 18:10:24 +0800 Subject: [PATCH 09/12] test: add tests Signed-off-by: tygao --- .../workspace_creator.test.tsx | 78 ++++++++ .../association_data_source_modal.test.tsx | 28 +++ .../connection_type_icon.test.tsx.snap | 17 ++ .../connection_type_icon.test.tsx | 16 ++ src/plugins/workspace/public/utils.test.ts | 175 +++++++++++++++++- 5 files changed, 312 insertions(+), 2 deletions(-) create mode 100644 src/plugins/workspace/public/components/workspace_form/__snapshots__/connection_type_icon.test.tsx.snap create mode 100644 src/plugins/workspace/public/components/workspace_form/connection_type_icon.test.tsx diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.test.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.test.tsx index 241220ee685..405d7d3b70a 100644 --- a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.test.tsx +++ b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.test.tsx @@ -55,6 +55,19 @@ const dataSourcesList = [ return 'ds2'; }, }, + { + id: 'id3', + title: 'dqs1', + description: 'Description of data connection 1', + auth: '', + dataSourceEngineType: '' as DataSourceEngineType, + workspaces: [], + type: 'data-connection', + connectionType: 'AWS Security Lake', + get: () => { + return 'ds2'; + }, + }, ]; const dataSourceConnectionsList = [ @@ -71,6 +84,13 @@ const dataSourceConnectionsList = [ connectionType: DataSourceConnectionType.OpenSearchConnection, type: 'OpenSearch', }, + { + id: 'id3', + name: 'dqs1', + description: 'Description of data connection 1', + connectionType: DataSourceConnectionType.DataConnection, + type: 'AWS Security Lake', + }, ]; const mockCoreStart = coreMock.createStart(); @@ -395,6 +415,64 @@ describe('WorkspaceCreator', () => { expect(notificationToastsAddDanger).not.toHaveBeenCalled(); }); + it('create workspace with customized selected data connections', async () => { + Object.defineProperty(HTMLElement.prototype, 'offsetHeight', { + configurable: true, + value: 600, + }); + Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { + configurable: true, + value: 600, + }); + const { getByTestId, getAllByText, getByText } = render( + + ); + + // Ensure workspace create form rendered + await waitFor(() => { + expect(getByTestId('workspaceForm-bottomBar-createButton')).toBeInTheDocument(); + }); + const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText'); + fireEvent.input(nameInput, { + target: { value: 'test workspace name' }, + }); + fireEvent.click(getByTestId('workspaceUseCase-observability')); + fireEvent.click(getByTestId('workspace-creator-dqc-assign-button')); + await waitFor(() => { + expect( + getByText( + 'Add data sources that will be available in the workspace. If a selected data source has related Direct Query connection, they will also be available in the workspace.' + ) + ).toBeInTheDocument(); + expect(getByText(dataSourcesList[2].title)).toBeInTheDocument(); + }); + fireEvent.click(getByText(dataSourcesList[2].title)); + fireEvent.click(getAllByText('Associate data sources')[1]); + + fireEvent.click(getByTestId('workspaceForm-bottomBar-createButton')); + expect(workspaceClientCreate).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'test workspace name', + }), + { + dataConnections: ['id3'], + dataSources: [], + permissions: { + library_write: { + users: ['%me%'], + }, + write: { + users: ['%me%'], + }, + }, + } + ); + await waitFor(() => { + expect(notificationToastsAddSuccess).toHaveBeenCalled(); + }); + expect(notificationToastsAddDanger).not.toHaveBeenCalled(); + }); + it('should not create workspace API when submitting', async () => { workspaceClientCreate.mockImplementationOnce( () => diff --git a/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.test.tsx b/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.test.tsx index 7a35d449730..a1bf6502e7b 100644 --- a/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.test.tsx +++ b/src/plugins/workspace/public/components/workspace_detail/association_data_source_modal.test.tsx @@ -52,6 +52,12 @@ const setupAssociationDataSourceModal = ({ connectionType: DataSourceConnectionType.OpenSearchConnection, type: 'OpenSearch', }, + { + id: 'dqs1', + name: 'Data Connection 1', + connectionType: DataSourceConnectionType.DataConnection, + type: 'AWS Security Lake', + }, ]); const { logos } = chromeServiceMock.createStartContract(); render( @@ -178,4 +184,26 @@ describe('AssociationDataSourceModal', () => { }, ]); }); + + it('should call handleAssignDataSourceConnections with data connections after assigned', async () => { + const handleAssignDataSourceConnectionsMock = jest.fn(); + setupAssociationDataSourceModal({ + handleAssignDataSourceConnections: handleAssignDataSourceConnectionsMock, + mode: AssociationDataSourceModalMode.DirectQueryConnections, + }); + + await waitFor(() => { + fireEvent.click(screen.getByRole('option', { name: 'Data Connection 1' })); + fireEvent.click(screen.getByRole('button', { name: 'Associate data sources' })); + }); + + expect(handleAssignDataSourceConnectionsMock).toHaveBeenCalledWith([ + { + id: 'dqs1', + name: 'Data Connection 1', + connectionType: DataSourceConnectionType.DataConnection, + type: 'AWS Security Lake', + }, + ]); + }); }); diff --git a/src/plugins/workspace/public/components/workspace_form/__snapshots__/connection_type_icon.test.tsx.snap b/src/plugins/workspace/public/components/workspace_form/__snapshots__/connection_type_icon.test.tsx.snap new file mode 100644 index 00000000000..04c42042988 --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_form/__snapshots__/connection_type_icon.test.tsx.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ConnectionTypeIcon should render normally 1`] = ``; + +exports[`ConnectionTypeIcon should render normally 2`] = ` + + + + + +`; diff --git a/src/plugins/workspace/public/components/workspace_form/connection_type_icon.test.tsx b/src/plugins/workspace/public/components/workspace_form/connection_type_icon.test.tsx new file mode 100644 index 00000000000..569dc9b8c3d --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_form/connection_type_icon.test.tsx @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { ConnectionTypeIcon } from './connection_type_icon'; + +describe('ConnectionTypeIcon', () => { + it('should render normally', () => { + expect(mount()).toMatchSnapshot(); + expect(mount()).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/workspace/public/utils.test.ts b/src/plugins/workspace/public/utils.test.ts index c86485f6a03..f6b5ff28d92 100644 --- a/src/plugins/workspace/public/utils.test.ts +++ b/src/plugins/workspace/public/utils.test.ts @@ -15,13 +15,21 @@ import { isEqualWorkspaceUseCase, prependWorkspaceToBreadcrumbs, getIsOnlyAllowEssentialUseCase, + mergeDataSourcesWithConnections, } from './utils'; import { WorkspaceAvailability } from '../../../core/public'; import { coreMock } from '../../../core/public/mocks'; import { WORKSPACE_DETAIL_APP_ID, USE_CASE_PREFIX } from '../common/constants'; -import { SigV4ServiceName } from '../../../plugins/data_source/common/data_sources'; +import { + SigV4ServiceName, + DataSourceEngineType, +} from '../../../plugins/data_source/common/data_sources'; import { createMockedRegisteredUseCases } from './mocks'; -import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../data_source/common'; +import { + DATA_SOURCE_SAVED_OBJECT_TYPE, + DATA_CONNECTION_SAVED_OBJECT_TYPE, +} from '../../data_source/common'; +import { DataSourceConnectionType } from '../common/types'; const startMock = coreMock.createStart(); const STATIC_USE_CASES = createMockedRegisteredUseCases(); @@ -408,6 +416,61 @@ describe('workspace utils: getDataSourcesList', () => { ]); }); + it('should return title for data source object and connectionId as title for data connection object', async () => { + mockedSavedObjectClient.find = jest.fn().mockResolvedValue({ + savedObjects: [ + { + id: 'id1', + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + get: (param: string) => { + switch (param) { + case 'title': + return 'title1'; + default: + return 'mock_value'; + } + }, + }, + { + id: 'id2', + type: DATA_CONNECTION_SAVED_OBJECT_TYPE, + get: (param: string) => { + switch (param) { + case 'connectionId': + return 'connectionId1'; + case 'title': + return undefined; + default: + return 'mock_value'; + } + }, + }, + ], + }); + expect(await getDataSourcesList(mockedSavedObjectClient, [])).toStrictEqual([ + { + id: 'id1', + title: 'title1', + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + auth: 'mock_value', + description: 'mock_value', + dataSourceEngineType: 'mock_value', + connectionType: 'mock_value', + workspaces: [], + }, + { + id: 'id2', + title: 'connectionId1', + type: DATA_CONNECTION_SAVED_OBJECT_TYPE, + auth: 'mock_value', + description: 'mock_value', + dataSourceEngineType: 'mock_value', + connectionType: 'mock_value', + workspaces: [], + }, + ]); + }); + it('should return empty array if no saved objects responded', async () => { mockedSavedObjectClient.find = jest.fn().mockResolvedValue({}); expect(await getDataSourcesList(mockedSavedObjectClient, [])).toStrictEqual([]); @@ -748,3 +811,111 @@ describe('workspace utils: prependWorkspaceToBreadcrumbs', () => { expect(enrichedBreadcrumbs?.[1].text).toEqual(workspaceWithAllUseCase.name); }); }); + +describe('workspace utils: mergeDataSourcesWithConnections', () => { + it('should merge data sources with direct query connections', () => { + const dataSources = [ + { + id: 'id1', + title: 'title1', + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + dataSourceEngineType: 'OpenSearch' as DataSourceEngineType, + description: '', + }, + { + id: 'id2', + title: 'title2', + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + dataSourceEngineType: 'OpenSearch' as DataSourceEngineType, + description: '', + }, + ]; + const directQueryConnections = [ + { + id: 'id3', + title: 'title3', + name: 'name1', + parentId: 'id1', + description: 'direct_query_connections_1', + type: 'Amazon S3', + connectionType: DataSourceConnectionType.DirectQueryConnection, + }, + ]; + const result = mergeDataSourcesWithConnections(dataSources, directQueryConnections); + expect(result).toStrictEqual([ + { + connectionType: 1, + id: 'id3', + name: 'name1', + parentId: 'id1', + title: 'title3', + description: 'direct_query_connections_1', + type: 'Amazon S3', + }, + { + connectionType: 0, + description: '', + id: 'id1', + name: 'title1', + relatedConnections: [ + { + connectionType: 1, + id: 'id3', + name: 'name1', + parentId: 'id1', + title: 'title3', + type: 'Amazon S3', + description: 'direct_query_connections_1', + }, + ], + type: 'OpenSearch', + }, + { + connectionType: 0, + description: '', + id: 'id2', + name: 'title2', + relatedConnections: [], + type: 'OpenSearch', + }, + ]); + }); + + it('should not merge data sources or data connections if no direct query connections', () => { + const dataSources = [ + { + id: 'id1', + title: 'title1', + dataSourceEngineType: 'OpenSearch' as DataSourceEngineType, + description: '', + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + }, + { + id: 'id2', + title: 'title2', + type: DATA_CONNECTION_SAVED_OBJECT_TYPE, + connectionType: 'AWS CloudWatch', + description: '', + }, + ]; + + const result = mergeDataSourcesWithConnections(dataSources, []); + expect(result).toStrictEqual([ + { + connectionType: 0, + description: '', + id: 'id1', + name: 'title1', + relatedConnections: [], + type: 'OpenSearch', + }, + { + connectionType: 2, + description: '', + id: 'id2', + name: 'title2', + type: 'AWS CloudWatch', + }, + ]); + }); +}); From a78d01a1cb2bf14948459d68a5b05d98ed92685b Mon Sep 17 00:00:00 2001 From: tygao Date: Wed, 18 Sep 2024 18:17:22 +0800 Subject: [PATCH 10/12] display text directly instead of link for data connection in table Signed-off-by: tygao --- .../workspace_form/data_source_connection_table.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plugins/workspace/public/components/workspace_form/data_source_connection_table.tsx b/src/plugins/workspace/public/components/workspace_form/data_source_connection_table.tsx index 57b3ba666cb..18c9fe308c9 100644 --- a/src/plugins/workspace/public/components/workspace_form/data_source_connection_table.tsx +++ b/src/plugins/workspace/public/components/workspace_form/data_source_connection_table.tsx @@ -115,6 +115,10 @@ export const DataSourceConnectionTable = forwardRef< }), truncateText: true, render: (name: string, record) => { + // There is not a detail page for data connection, so we won't display a link here. + if (record.connectionType === DataSourceConnectionType.DataConnection) { + return name; + } let url: string; if (record.connectionType === DataSourceConnectionType.OpenSearchConnection) { url = http.basePath.prepend(`/app/dataSources/${record.id}`); From db41ee27996694c4a01b91451d884fc2fe5bab17 Mon Sep 17 00:00:00 2001 From: Tianyu Gao Date: Fri, 20 Sep 2024 13:10:17 +0800 Subject: [PATCH 11/12] Apply suggestions from code review Co-authored-by: SuZhou-Joe Signed-off-by: Tianyu Gao --- src/plugins/workspace/public/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index 111f5287fc3..adbf44155e4 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -255,7 +255,7 @@ export const getDataSourcesList = ( const description = source.get('description'); const dataSourceEngineType = source.get('dataSourceEngineType'); const type = source.type; - // This is a filed only for detail type of data connection in order not to mix saved object type. + // This is a field only for detail type of data connection in order not to mix saved object type. const connectionType = source.get('type'); return { id, From c87b2d892b0b4993426f5658013ff400227ee0ed Mon Sep 17 00:00:00 2001 From: tygao Date: Fri, 20 Sep 2024 16:39:58 +0800 Subject: [PATCH 12/12] update Signed-off-by: tygao --- .../workspace_detail_connection_table.tsx | 1 + src/plugins/workspace/public/utils.ts | 47 ++++++++++--------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx b/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx index bd0fd321e22..d30fd9252e5 100644 --- a/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx +++ b/src/plugins/workspace/public/components/workspace_detail/workspace_detail_connection_table.tsx @@ -9,6 +9,7 @@ import { i18n } from '@osd/i18n'; import { DataSourceConnection, DataSourceConnectionType } from '../../../common/types'; import { AssociationDataSourceModalMode } from '../../../common/constants'; import { DataSourceConnectionTable } from '../workspace_form'; + interface WorkspaceDetailConnectionTableProps { isDashboardAdmin: boolean; connectionType: string; diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index adbf44155e4..8dd5a0b1d24 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -255,7 +255,7 @@ export const getDataSourcesList = ( const description = source.get('description'); const dataSourceEngineType = source.get('dataSourceEngineType'); const type = source.type; - // This is a field only for detail type of data connection in order not to mix saved object type. + // This is a field only for detail type of data connection in order not to mix with saved object type. const connectionType = source.get('type'); return { id, @@ -290,10 +290,10 @@ export const getDirectQueryConnections = async (dataSourceId: string, http: Http return directQueryConnections; }; -export const convertDataSourcesToOpenSearchConnections = ( +export const convertDataSourcesToOpenSearchAndDataConnections = ( dataSources: DataConnection[] | DataSource[] -): DataSourceConnection[] => - dataSources +): Record<'openSearchConnections' | 'dataConnections', DataSourceConnection[]> => { + const openSearchConnections = dataSources .filter((ds) => ds.type === DATA_SOURCE_SAVED_OBJECT_TYPE) .map((ds: DataSource) => { return { @@ -305,22 +305,21 @@ export const convertDataSourcesToOpenSearchConnections = ( relatedConnections: [], }; }); - -export const convertDataSourcesToDataConnections = ( - dataSources: DataConnection[] | DataSource[] -): DataSourceConnection[] => { - const dataConnections = dataSources.filter( - (ds) => ds.type === DATA_CONNECTION_SAVED_OBJECT_TYPE - ) as DataConnection[]; - return dataConnections.map((ds) => { - return { - id: ds.id, - type: ds.connectionType, - connectionType: DataSourceConnectionType.DataConnection, - name: ds.title, - description: ds.description, - }; - }); + const dataConnections = dataSources + .filter((ds) => ds.type === DATA_CONNECTION_SAVED_OBJECT_TYPE) + .map((ds) => { + return { + id: ds.id, + type: (ds as DataConnection).connectionType, + connectionType: DataSourceConnectionType.DataConnection, + name: ds.title, + description: ds.description, + }; + }); + return { + openSearchConnections, + dataConnections, + }; }; export const fulfillRelatedConnections = ( @@ -343,13 +342,15 @@ export const mergeDataSourcesWithConnections = ( dataSources: DataSource[] | DataConnection[], directQueryConnections: DataSourceConnection[] ): DataSourceConnection[] => { - const openSearchConnections = convertDataSourcesToOpenSearchConnections(dataSources); - const dataConnections = convertDataSourcesToDataConnections(dataSources); + const { + openSearchConnections, + dataConnections, + } = convertDataSourcesToOpenSearchAndDataConnections(dataSources); const result = [ ...fulfillRelatedConnections(openSearchConnections, directQueryConnections), ...directQueryConnections, ...dataConnections, - ].sort((a, b) => a?.name?.localeCompare(b?.name)); + ].sort((a, b) => a.name.localeCompare(b.name)); return result; };