From 289a7c0c1a1f0b4eec0d1b9608ff4f9c7f77c6b8 Mon Sep 17 00:00:00 2001 From: Jialiang Liang Date: Wed, 24 Jul 2024 09:22:58 -0700 Subject: [PATCH] [2.16] Fix minor issues for Direct Query Datasource (#7419) * Switch the tab orders Signed-off-by: Ryan Liang * Add delete with MDS support Signed-off-by: Ryan Liang * Add acc creation action in data connection table Signed-off-by: Ryan Liang * Fix snapshots Signed-off-by: Ryan Liang * mute the table row for integration action when feature flag is disabled and no obs installed Signed-off-by: Ryan Liang * add integration flyout in dataconnection table Signed-off-by: Ryan Liang * Add discover forwarding action in connections table Signed-off-by: Ryan Liang * Disable query in observability logs in data connections table action menu Signed-off-by: Ryan Liang * Update the test of connection table Signed-off-by: Ryan Liang * Update the test of home panel Signed-off-by: Ryan Liang * Fix the then for checking observability dashboards Signed-off-by: Ryan Liang * Fix delete Signed-off-by: Ryan Liang * fix check plugin Signed-off-by: Ryan Liang * fix check plugin 2 Signed-off-by: Ryan Liang * fix check plugin 3 test Signed-off-by: Ryan Liang --------- Signed-off-by: Ryan Liang --- .../data_source_home_panel.test.tsx.snap | 22 +- .../data_source_home_panel.test.tsx | 8 +- .../data_source_home_panel.tsx | 14 +- .../direct_query_connection_detail.test.tsx | 22 +- .../direct_query_connection_detail.tsx | 40 +- ...query_data_connections_table.test.tsx.snap | 1306 ++++++----------- ...rect_query_data_connections_table.test.tsx | 193 +-- ...ge_direct_query_data_connections_table.tsx | 92 +- .../public/components/utils.ts | 20 + .../server/routes/data_connections_router.ts | 46 + 10 files changed, 718 insertions(+), 1045 deletions(-) diff --git a/src/plugins/data_source_management/public/components/data_source_home_panel/__snapshots__/data_source_home_panel.test.tsx.snap b/src/plugins/data_source_management/public/components/data_source_home_panel/__snapshots__/data_source_home_panel.test.tsx.snap index 4db09dab5b2a..40402f99f0a0 100644 --- a/src/plugins/data_source_management/public/components/data_source_home_panel/__snapshots__/data_source_home_panel.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/data_source_home_panel/__snapshots__/data_source_home_panel.test.tsx.snap @@ -44,17 +44,17 @@ exports[`DataSourceHomePanel renders correctly 1`] = ` - Direct query connections + OpenSearch connections - OpenSearch connections + Direct query connections @@ -62,12 +62,14 @@ exports[`DataSourceHomePanel renders correctly 1`] = ` - diff --git a/src/plugins/data_source_management/public/components/data_source_home_panel/data_source_home_panel.test.tsx b/src/plugins/data_source_management/public/components/data_source_home_panel/data_source_home_panel.test.tsx index 017f450c11ae..1f451b1531e9 100644 --- a/src/plugins/data_source_management/public/components/data_source_home_panel/data_source_home_panel.test.tsx +++ b/src/plugins/data_source_management/public/components/data_source_home_panel/data_source_home_panel.test.tsx @@ -67,23 +67,23 @@ describe('DataSourceHomePanel', () => { test('renders DataSourceTableWithRouter when manageOpensearchDataSources tab is selected', () => { const wrapper = mountComponent(); - wrapper.find(EuiTab).at(1).simulate('click'); + wrapper.find(EuiTab).at(0).simulate('click'); expect(wrapper.find(DataSourceTableWithRouter)).toHaveLength(1); expect(wrapper.find(ManageDirectQueryDataConnectionsTable)).toHaveLength(0); }); test('renders ManageDirectQueryDataConnectionsTable when manageDirectQueryDataSources tab is selected', () => { const wrapper = mountComponent(); - wrapper.find(EuiTab).at(0).simulate('click'); + wrapper.find(EuiTab).at(1).simulate('click'); expect(wrapper.find(ManageDirectQueryDataConnectionsTable)).toHaveLength(1); expect(wrapper.find(DataSourceTableWithRouter)).toHaveLength(0); }); test('handles tab changes', () => { const wrapper = mountComponent(); - expect(wrapper.find(ManageDirectQueryDataConnectionsTable)).toHaveLength(1); - wrapper.find(EuiTab).at(1).simulate('click'); expect(wrapper.find(DataSourceTableWithRouter)).toHaveLength(1); + wrapper.find(EuiTab).at(1).simulate('click'); + expect(wrapper.find(ManageDirectQueryDataConnectionsTable)).toHaveLength(1); }); test('does not render OpenSearch connections tab when featureFlagStatus is false', () => { diff --git a/src/plugins/data_source_management/public/components/data_source_home_panel/data_source_home_panel.tsx b/src/plugins/data_source_management/public/components/data_source_home_panel/data_source_home_panel.tsx index 219e37e93345..b90c3484654e 100644 --- a/src/plugins/data_source_management/public/components/data_source_home_panel/data_source_home_panel.tsx +++ b/src/plugins/data_source_management/public/components/data_source_home_panel/data_source_home_panel.tsx @@ -39,7 +39,10 @@ export const DataSourceHomePanel: React.FC = ({ application, } = useOpenSearchDashboards().services; - const [selectedTabId, setSelectedTabId] = useState('manageDirectQueryDataSources'); + const defaultTabId = featureFlagStatus + ? 'manageOpensearchDataSources' + : 'manageDirectQueryDataSources'; + const [selectedTabId, setSelectedTabId] = useState(defaultTabId); const canManageDataSource = !!application.capabilities?.dataSource?.canManage; useEffect(() => { @@ -51,10 +54,6 @@ export const DataSourceHomePanel: React.FC = ({ }; const tabs = [ - { - id: 'manageDirectQueryDataSources', - name: 'Direct query connections', - }, ...(featureFlagStatus ? [ { @@ -63,6 +62,10 @@ export const DataSourceHomePanel: React.FC = ({ }, ] : []), + { + id: 'manageDirectQueryDataSources', + name: 'Direct query connections', + }, ]; const renderTabs = () => { @@ -110,6 +113,7 @@ export const DataSourceHomePanel: React.FC = ({ savedObjects={savedObjects} uiSettings={uiSettings} featureFlagStatus={featureFlagStatus} + application={application} /> )} diff --git a/src/plugins/data_source_management/public/components/direct_query_data_sources_components/connection_detail/direct_query_connection_detail.test.tsx b/src/plugins/data_source_management/public/components/direct_query_data_sources_components/connection_detail/direct_query_connection_detail.test.tsx index be1a93c5572b..ea13ffd2568f 100644 --- a/src/plugins/data_source_management/public/components/direct_query_data_sources_components/connection_detail/direct_query_connection_detail.test.tsx +++ b/src/plugins/data_source_management/public/components/direct_query_data_sources_components/connection_detail/direct_query_connection_detail.test.tsx @@ -9,6 +9,7 @@ import '@testing-library/jest-dom'; import { MemoryRouter, Route } from 'react-router-dom'; import { DirectQueryDataConnectionDetail } from './direct_query_connection_detail'; import { ApplicationStart, HttpStart, NotificationsStart } from 'opensearch-dashboards/public'; +import { isPluginInstalled } from '../../utils'; jest.mock('../../../constants', () => ({ DATACONNECTIONS_BASE: '/api/dataconnections', @@ -61,19 +62,9 @@ jest.mock('../associated_object_management/utils/associated_objects_tab_utils', redirectToExplorerS3: jest.fn(), })); -beforeAll(() => { - global.fetch = jest.fn(() => - Promise.resolve({ - json: () => - Promise.resolve({ status: { statuses: [{ id: 'plugin:observabilityDashboards' }] } }), - }) - ) as jest.Mock; -}); - -afterAll(() => { - global.fetch.mockClear(); - delete global.fetch; -}); +jest.mock('../../utils', () => ({ + isPluginInstalled: jest.fn(), +})); const renderComponent = ({ featureFlagStatus = false, @@ -98,6 +89,11 @@ const renderComponent = ({ }; describe('DirectQueryDataConnectionDetail', () => { + beforeEach(() => { + jest.clearAllMocks(); + (isPluginInstalled as jest.Mock).mockResolvedValue(true); + }); + test('renders without crashing', async () => { const mockHttp = { get: jest.fn().mockResolvedValue({ diff --git a/src/plugins/data_source_management/public/components/direct_query_data_sources_components/connection_detail/direct_query_connection_detail.tsx b/src/plugins/data_source_management/public/components/direct_query_data_sources_components/connection_detail/direct_query_connection_detail.tsx index bc0869dfc2e5..c97ea554544d 100644 --- a/src/plugins/data_source_management/public/components/direct_query_data_sources_components/connection_detail/direct_query_connection_detail.tsx +++ b/src/plugins/data_source_management/public/components/direct_query_data_sources_components/connection_detail/direct_query_connection_detail.tsx @@ -53,6 +53,7 @@ import { IntegrationInstancesSearchResult, } from '../../../../framework/types'; import { INTEGRATIONS_BASE } from '../../../../framework/utils/shared'; +import { isPluginInstalled } from '../../utils'; interface DirectQueryDataConnectionDetailProps { featureFlagStatus: boolean; @@ -70,41 +71,6 @@ export const DirectQueryDataConnectionDetail: React.FC { const [observabilityDashboardsExists, setObservabilityDashboardsExists] = useState(false); - const checkIfSQLWorkbenchPluginIsInstalled = () => { - fetch('/api/status', { - headers: { - 'Content-Type': 'application/json', - 'osd-xsrf': 'true', - 'accept-language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,zh-TW;q=0.6', - pragma: 'no-cache', - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-origin', - }, - method: 'GET', - referrerPolicy: 'strict-origin-when-cross-origin', - mode: 'cors', - credentials: 'include', - }) - .then(function (response) { - return response.json(); - }) - .then((data) => { - for (let i = 0; i < data.status.statuses.length; ++i) { - if (data.status.statuses[i].id.includes('plugin:observabilityDashboards')) { - setObservabilityDashboardsExists(true); - } - } - }) - .catch((error) => { - notifications.toasts.addDanger( - 'Error checking Dashboards Observability Plugin Installation status.' - ); - // eslint-disable-next-line no-console - console.error(error); - }); - }; - const { dataSourceName } = useParams<{ dataSourceName: string }>(); const { search } = useLocation(); const queryParams = new URLSearchParams(search); @@ -211,7 +177,9 @@ export const DirectQueryDataConnectionDetail: React.FC { - checkIfSQLWorkbenchPluginIsInstalled(); + isPluginInstalled('plugin:observabilityDashboards', notifications, http).then( + setObservabilityDashboardsExists + ); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_data_connection/__snapshots__/manage_direct_query_data_connections_table.test.tsx.snap b/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_data_connection/__snapshots__/manage_direct_query_data_connections_table.test.tsx.snap index 32bd004f3ee4..90ed2c90c3c1 100644 --- a/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_data_connection/__snapshots__/manage_direct_query_data_connections_table.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_data_connection/__snapshots__/manage_direct_query_data_connections_table.test.tsx.snap @@ -1,920 +1,476 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ManageDirectQueryDataConnectionsTable renders correctly 1`] = ` - - +
+
-
+
+
+
+ +
+ + +
+
+
+
+
+
- -
- +
-
+
+
+
+ + + + + + + + + + -
+ Name +
+
- - -
+
+
+ - - - - - -
- -
- - - - - - + connection1 + + + -
- + + + -
-
- +
+ Name +
+
+
+
+ +
- -
- -
- - -
- -
- + connection2 +
- - +
+ +
+ + + +
+
+ + + Name + + + + + + Status + + + + + + Actions + + +
- +
+
-
-
- - - - -
- - - - - -
-
-
+
-
- +
+ Active +
+
+
- - - - -
- - - +
+
+ + + + Delete + + +
+
+
+ Status +
+
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - Name - - - - - - - - - - - - Status - - - - - - - - - - - - Actions - - - - - -
-
- - No items found - -
-
- +
+ +
+
+ Inactive +
+
+
+
+
+ + + Accelerate performance + + + + + Delete + + +
+
+
+
+
+
+
+
+
+
- - +
+
+
+ +
- +
- +
- - +
+ `; diff --git a/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_data_connection/manage_direct_query_data_connections_table.test.tsx b/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_data_connection/manage_direct_query_data_connections_table.test.tsx index 06d1dc61d166..aa511d65009e 100644 --- a/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_data_connection/manage_direct_query_data_connections_table.test.tsx +++ b/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_data_connection/manage_direct_query_data_connections_table.test.tsx @@ -4,125 +4,134 @@ */ import React from 'react'; -import { act } from 'react-dom/test-utils'; -import { mount, ReactWrapper } from 'enzyme'; -import { - HttpStart, - NotificationsStart, - SavedObjectsStart, - IUiSettingsClient, -} from 'opensearch-dashboards/public'; -import { EuiFieldSearch, EuiInMemoryTable, EuiLoadingSpinner } from '@elastic/eui'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { ManageDirectQueryDataConnectionsTable } from './manage_direct_query_data_connections_table'; -const mockHttp = ({ - get: jest.fn(), -} as unknown) as jest.Mocked; +// Mock dependencies +jest.mock('react-router-dom', () => ({ + useHistory: () => ({ + push: jest.fn(), + }), +})); -const mockNotifications = ({ - toasts: { addDanger: jest.fn(), addSuccess: jest.fn(), addWarning: jest.fn() }, -} as unknown) as jest.Mocked; +jest.mock('../../../plugin', () => ({ + getRenderCreateAccelerationFlyout: jest.fn(() => jest.fn()), +})); -const mockSavedObjects = ({ - client: {}, -} as unknown) as jest.Mocked; - -const mockUiSettings = ({} as unknown) as jest.Mocked; - -const defaultProps = { - http: mockHttp, - notifications: mockNotifications, - savedObjects: mockSavedObjects, - uiSettings: mockUiSettings, - featureFlagStatus: true, -}; +jest.mock('../icons/prometheus_logo.svg', () => 'prometheusLogo'); +jest.mock('../icons/s3_logo.svg', () => 's3Logo'); +jest.mock('../integrations/installed_integrations_table', () => ({ + InstallIntegrationFlyout: jest.fn(() =>
MockInstallIntegrationFlyout
), +})); describe('ManageDirectQueryDataConnectionsTable', () => { - let wrapper: ReactWrapper; + const mockHttp = { get: jest.fn(), delete: jest.fn() }; + const mockNotifications = { toasts: { addSuccess: jest.fn(), addDanger: jest.fn() } }; + const mockSavedObjects = { client: {} }; + const mockUiSettings = {}; + const mockApplication = { navigateToApp: jest.fn() }; + + const defaultProps = { + http: mockHttp, + notifications: mockNotifications, + savedObjects: mockSavedObjects, + uiSettings: mockUiSettings, + featureFlagStatus: false, + application: mockApplication, + }; beforeEach(() => { jest.clearAllMocks(); }); - test('renders correctly', async () => { - mockHttp.get.mockResolvedValueOnce([]); // Mock an empty array for the initial fetch + test('renders data connections', async () => { + mockHttp.get.mockResolvedValue([ + { name: 'connection1', connector: 'PROMETHEUS', status: 'ACTIVE' }, + { name: 'connection2', connector: 'S3GLUE', status: 'INACTIVE' }, + ]); - await act(async () => { - wrapper = mount(); - }); - wrapper.update(); - expect(wrapper).toMatchSnapshot(); + render(); + await waitFor(() => expect(screen.getByText('connection1')).toBeInTheDocument()); + expect(screen.getByText('connection2')).toBeInTheDocument(); }); - test('displays loading spinner while fetching data', async () => { - let resolveFetch; - const fetchPromise = new Promise((resolve) => { - resolveFetch = resolve; - }); - mockHttp.get.mockImplementation(() => fetchPromise); - - await act(async () => { - wrapper = mount(); - }); + test('handles search input change', async () => { + mockHttp.get.mockResolvedValue([ + { name: 'connection1', connector: 'PROMETHEUS', status: 'ACTIVE' }, + { name: 'connection2', connector: 'S3GLUE', status: 'INACTIVE' }, + ]); - wrapper.update(); + render(); + await waitFor(() => expect(screen.getByText('connection1')).toBeInTheDocument()); - expect(wrapper.find(EuiLoadingSpinner).exists()).toBe(true); + const searchInput = screen.getByPlaceholderText('Search...'); + fireEvent.change(searchInput, { target: { value: 'connection2' } }); - // Simulate fetch completion - await act(async () => { - resolveFetch([]); - }); - wrapper.update(); + expect(screen.queryByText('connection1')).not.toBeInTheDocument(); + expect(screen.getByText('connection2')).toBeInTheDocument(); }); - test('fetches and displays data connections', async () => { - mockHttp.get.mockResolvedValueOnce([ - { - name: 'Test Connection', - connector: 'S3GLUE', - status: 'ACTIVE', - }, - ]); + test('displays error on failed fetch', async () => { + mockHttp.get.mockRejectedValue(new Error('Fetch error')); - await act(async () => { - wrapper = mount(); - }); - wrapper.update(); + render(); - expect(wrapper.find(EuiInMemoryTable).prop('items')).toHaveLength(1); + await waitFor(() => + expect(mockNotifications.toasts.addDanger).toHaveBeenCalledWith( + 'Could not fetch data sources' + ) + ); }); - test('filters data connections based on search text', async () => { - mockHttp.get.mockResolvedValueOnce([ - { - name: 'Test Connection', - connector: 'S3GLUE', - status: 'ACTIVE', - }, - { - name: 'Another Connection', - connector: 'PROMETHEUS', - status: 'INACTIVE', - }, + test('matches snapshot', async () => { + mockHttp.get.mockResolvedValue([ + { name: 'connection1', connector: 'PROMETHEUS', status: 'ACTIVE' }, + { name: 'connection2', connector: 'S3GLUE', status: 'INACTIVE' }, ]); - await act(async () => { - wrapper = mount(); - }); - wrapper.update(); + const { asFragment } = render(); + await waitFor(() => expect(screen.getByText('connection1')).toBeInTheDocument()); + expect(asFragment()).toMatchSnapshot(); + }); + + // Conditional rendering tests + test('renders no connections message when there are no connections', async () => { + mockHttp.get.mockResolvedValue([]); - const searchInput = wrapper.find(EuiFieldSearch); - expect(searchInput.exists()).toBe(true); + render(); + await waitFor(() => expect(screen.getByText('No items found')).toBeInTheDocument()); + }); - await act(async () => { - searchInput.find('input').simulate('change', { target: { value: 'Test' } }); - }); - wrapper.update(); + test('renders error message when fetch fails', async () => { + mockHttp.get.mockRejectedValue(new Error('Fetch error')); + + render(); + await waitFor(() => + expect(mockNotifications.toasts.addDanger).toHaveBeenCalledWith( + 'Could not fetch data sources' + ) + ); + }); - const table = wrapper.find(EuiInMemoryTable); - const items = table.prop('items') as any[]; - expect(items).toHaveLength(1); - expect(items[0].name).toBe('Test Connection'); + test('displays loading indicator while fetching data', async () => { + mockHttp.get.mockImplementation( + () => + new Promise((resolve) => { + setTimeout( + () => + resolve([ + { name: 'connection1', connector: 'PROMETHEUS', status: 'ACTIVE' }, + { name: 'connection2', connector: 'S3GLUE', status: 'INACTIVE' }, + ]), + 1000 + ); + }) + ); + + render(); + expect(screen.getByText('Loading direct query data connections...')).toBeInTheDocument(); + + await waitFor(() => expect(screen.getByText('connection1')).toBeInTheDocument()); + expect(screen.queryByText('Loading direct query data connections...')).not.toBeInTheDocument(); }); }); diff --git a/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_data_connection/manage_direct_query_data_connections_table.tsx b/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_data_connection/manage_direct_query_data_connections_table.tsx index e6ad73b88a21..4ec4e9a7fe8f 100644 --- a/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_data_connection/manage_direct_query_data_connections_table.tsx +++ b/src/plugins/data_source_management/public/components/direct_query_data_sources_components/direct_query_data_connection/manage_direct_query_data_connections_table.tsx @@ -20,6 +20,7 @@ import { } from '@elastic/eui'; import React, { useCallback, useEffect, useState } from 'react'; import { + ApplicationStart, HttpStart, IUiSettingsClient, NotificationsStart, @@ -36,7 +37,11 @@ import PrometheusLogo from '../icons/prometheus_logo.svg'; import S3Logo from '../icons/s3_logo.svg'; import { DataSourceSelector } from '../../data_source_selector'; import { DataSourceOption } from '../../data_source_menu/types'; -import { DATACONNECTIONS_BASE } from '../../../constants'; +import { DATACONNECTIONS_BASE, observabilityMetricsID } from '../../../constants'; +import { getRenderCreateAccelerationFlyout } from '../../../plugin'; +import { InstallIntegrationFlyout } from '../integrations/installed_integrations_table'; +import { redirectToExplorerS3 } from '../associated_object_management/utils/associated_objects_tab_utils'; +import { isPluginInstalled } from '../../utils'; interface DataConnection { connectionType: DirectQueryDatasourceType; @@ -50,6 +55,7 @@ interface ManageDirectQueryDataConnectionsTableProps { savedObjects: SavedObjectsStart; uiSettings: IUiSettingsClient; featureFlagStatus: boolean; + application: ApplicationStart; } // Custom truncate function @@ -64,10 +70,16 @@ export const ManageDirectQueryDataConnectionsTable: React.FC { + const [observabilityDashboardsExists, setObservabilityDashboardsExists] = useState(false); + const [showIntegrationsFlyout, setShowIntegrationsFlyout] = useState(false); + const [integrationsFlyout, setIntegrationsFlyout] = useState(null); + const [data, setData] = useState([]); const [isModalVisible, setIsModalVisible] = useState(false); const [modalLayout, setModalLayout] = useState(); + const [selectedConnection, setSelectedConnection] = useState(undefined); const [selectedDataSourceId, setSelectedDataSourceId] = useState(''); const [searchText, setSearchText] = useState(''); const [isLoading, setIsLoading] = useState(false); @@ -99,8 +111,42 @@ export const ManageDirectQueryDataConnectionsTable: React.FC { + if (!connectionName) return; + + const endpoint = + featureFlagStatus && selectedDataSourceId !== undefined + ? `${DATACONNECTIONS_BASE}/${connectionName}/dataSourceMDSId=${selectedDataSourceId}` + : `${DATACONNECTIONS_BASE}/${connectionName}`; + + setIsLoading(true); + + http + .delete(endpoint) + .then(() => { + notifications.toasts.addSuccess(`Data connection ${connectionName} deleted successfully`); + setData(data.filter((connection) => connection.name !== connectionName)); + }) + .catch((err) => { + notifications.toasts.addDanger( + `Data connection ${connectionName} not deleted. See output for more details.` + ); + }) + .finally(() => { + setIsLoading(false); + setSelectedConnection(undefined); // Clear the selected connection after deletion + }); + }, + [http, notifications.toasts, selectedDataSourceId, featureFlagStatus, data] + ); + useEffect(() => { fetchDataSources(); + isPluginInstalled('plugin:observabilityDashboards', notifications, http).then( + setObservabilityDashboardsExists + ); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [fetchDataSources]); const handleSelectedDataSourceChange = (e: DataSourceOption[]) => { @@ -108,19 +154,17 @@ export const ManageDirectQueryDataConnectionsTable: React.FC { - setData(data.filter((connection) => !(connection.name === connectionName))); - }; - const displayDeleteModal = (connectionName: string) => { + setSelectedConnection(connectionName); setModalLayout( { setIsModalVisible(false); - deleteConnection(connectionName); + deleteDataSources(connectionName); }} onCancel={() => { setIsModalVisible(false); + setSelectedConnection(undefined); // Clear the selected connection if cancel is clicked }} title={`Delete ${connectionName}`} message={`Are you sure you want to delete ${connectionName}?`} @@ -129,6 +173,8 @@ export const ManageDirectQueryDataConnectionsTable: React.FC @@ -138,7 +184,14 @@ export const ManageDirectQueryDataConnectionsTable: React.FC {}, + available: (datasource: DataConnection) => observabilityDashboardsExists, + onClick: (datasource: DataConnection) => { + if (datasource.connectionType === 'PROMETHEUS') { + application!.navigateToApp(observabilityMetricsID); + } else if (datasource.connectionType === 'S3GLUE') { + redirectToExplorerS3(datasource.name, application); + } + }, 'data-test-subj': 'action-query', }, { @@ -147,7 +200,12 @@ export const ManageDirectQueryDataConnectionsTable: React.FC datasource.connectionType !== 'PROMETHEUS', - onClick: () => {}, + onClick: (datasource: DataConnection) => { + renderCreateAccelerationFlyout({ + dataSourceName: datasource.name, + dataSourceMDSId: selectedDataSourceId, + }); + }, 'data-test-subj': 'action-accelerate', }, { @@ -155,8 +213,21 @@ export const ManageDirectQueryDataConnectionsTable: React.FC datasource.connectionType !== 'PROMETHEUS', - onClick: () => {}, + available: (datasource: DataConnection) => + !featureFlagStatus && + observabilityDashboardsExists && + datasource.connectionType !== 'PROMETHEUS', + onClick: (datasource: DataConnection) => { + setIntegrationsFlyout( + setShowIntegrationsFlyout(false)} + datasourceType={datasource.connectionType} + datasourceName={datasource.name} + http={http} + /> + ); + setShowIntegrationsFlyout(true); + }, 'data-test-subj': 'action-integrate', }, { @@ -283,6 +354,7 @@ export const ManageDirectQueryDataConnectionsTable: React.FC {isModalVisible && modalLayout} + {showIntegrationsFlyout && integrationsFlyout} ); }; diff --git a/src/plugins/data_source_management/public/components/utils.ts b/src/plugins/data_source_management/public/components/utils.ts index b1ccf2c9130c..848244fc67e8 100644 --- a/src/plugins/data_source_management/public/components/utils.ts +++ b/src/plugins/data_source_management/public/components/utils.ts @@ -11,6 +11,7 @@ import { ToastsStart, ApplicationStart, CoreStart, + NotificationsStart, } from 'src/core/public'; import { deepFreeze } from '@osd/std'; import uuid from 'uuid'; @@ -406,3 +407,22 @@ export const formatError = (name: string, message: string, details: string) => { }, }; }; + +export const isPluginInstalled = async ( + pluginId: string, + notifications: NotificationsStart, + http: HttpStart +): Promise => { + try { + const response = await http.get('/api/status'); + const pluginExists = response.status.statuses.some((status: { id: string }) => + status.id.includes(pluginId) + ); + return pluginExists; + } catch (error) { + notifications.toasts.addDanger(`Error checking ${pluginId} Plugin Installation status.`); + // eslint-disable-next-line no-console + console.error(error); + return false; + } +}; diff --git a/src/plugins/data_source_management/server/routes/data_connections_router.ts b/src/plugins/data_source_management/server/routes/data_connections_router.ts index 87b40b90dc16..6b8dc4b42054 100644 --- a/src/plugins/data_source_management/server/routes/data_connections_router.ts +++ b/src/plugins/data_source_management/server/routes/data_connections_router.ts @@ -291,4 +291,50 @@ export function registerDataConnectionsRoute(router: IRouter, dataSourceEnabled: } } ); + + router.delete( + { + path: `${DATACONNECTIONS_BASE}/{name}/dataSourceMDSId={dataSourceMDSId?}`, + validate: { + params: schema.object({ + name: schema.string(), + dataSourceMDSId: schema.maybe(schema.string({ defaultValue: '' })), + }), + }, + }, + async (context, request, response): Promise => { + const dataSourceMDSId = request.params.dataSourceMDSId; + try { + let dataConnectionsresponse; + if (dataSourceEnabled && dataSourceMDSId) { + const client = await context.dataSource.opensearch.legacy.getClient(dataSourceMDSId); + dataConnectionsresponse = await client.callAPI('ppl.deleteDataConnection', { + dataconnection: request.params.name, + }); + } else { + dataConnectionsresponse = await context.opensearch_data_source_management.dataSourceManagementClient + .asScoped(request) + .callAsCurrentUser('ppl.deleteDataConnection', { + dataconnection: request.params.name, + }); + } + return response.ok({ + body: dataConnectionsresponse, + }); + } catch (error: any) { + console.error('Issue in deleting data sources:', error); + const statusCode = error.statusCode || error.body?.statusCode || 500; + const errorBody = error.body || + error.response || { message: error.message || 'Unknown error occurred' }; + + return response.custom({ + statusCode, + body: { + error: errorBody, + message: errorBody.message || error.message, + }, + }); + } + } + ); }