Skip to content

Commit

Permalink
[MDS]Add MDS support for Integration (#8008) (#8112)
Browse files Browse the repository at this point in the history
* Add mds support for Integration



* Add mds support for Integration flyout only for creation



* Fix the installed integration table content



* Update snapshot



* Update to fix the local cluster integration fetch and add more test case



* Update release notes



* Changeset file for PR #8008 created/updated

---------



(cherry picked from commit 2999fdf)

Signed-off-by: Ryan Liang <jiallian@amazon.com>
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Sep 11, 2024
1 parent b57c2ae commit a87ce21
Show file tree
Hide file tree
Showing 9 changed files with 533 additions and 59 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/8008.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- [MDS]Add MDS support for Integration ([#8008](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8008))
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
- Support DQCs in create page ([#7961](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7961))
- [Workspace] Hide home breadcrumbs when in a workspace ([#7992](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7992))
- [Workspace]Deny get or bulkGet for global data source ([#8043](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8043))
- [MDS]Add MDS support for Integration #8008 ([#8008](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8008))

### 🐛 Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ import { render, screen, waitFor } from '@testing-library/react';
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';
import {
ApplicationStart,
HttpStart,
NotificationsStart,
SavedObjectsStart,
} from 'opensearch-dashboards/public';
import * as utils from '../../utils';

jest.mock('../../../constants', () => ({
DATACONNECTIONS_BASE: '/api/dataconnections',
Expand Down Expand Up @@ -64,24 +69,35 @@ jest.mock('../associated_object_management/utils/associated_objects_tab_utils',

jest.mock('../../utils', () => ({
isPluginInstalled: jest.fn(),
getDataSourcesWithFields: jest.fn(),
}));

const renderComponent = ({
featureFlagStatus = false,
http = {},
notifications = {},
notifications = {
toasts: {
addDanger: jest.fn(),
},
},
application = {},
setBreadcrumbs = jest.fn(),
savedObjects = {
client: {
find: jest.fn().mockResolvedValue({ saved_objects: [] }),
},
},
}) => {
return render(
<MemoryRouter initialEntries={['/dataconnections/test']}>
<MemoryRouter initialEntries={['/dataconnections/test?dataSourceMDSId=test-mdsid']}>
<Route path="/dataconnections/:dataSourceName">
<DirectQueryDataConnectionDetail
featureFlagStatus={featureFlagStatus}
http={http as HttpStart}
notifications={notifications as NotificationsStart}
application={application as ApplicationStart}
setBreadcrumbs={setBreadcrumbs}
savedObjects={savedObjects as SavedObjectsStart}
/>
</Route>
</MemoryRouter>
Expand All @@ -91,7 +107,7 @@ const renderComponent = ({
describe('DirectQueryDataConnectionDetail', () => {
beforeEach(() => {
jest.clearAllMocks();
(isPluginInstalled as jest.Mock).mockResolvedValue(true);
(utils.isPluginInstalled as jest.Mock).mockResolvedValue(true);
});

test('renders without crashing', async () => {
Expand Down Expand Up @@ -288,4 +304,89 @@ describe('DirectQueryDataConnectionDetail', () => {
expect(screen.getByText('Configure Integrations')).toBeInTheDocument();
expect(screen.getByText('Installed Integrations')).toBeInTheDocument();
});

test('filters integrations by references when featureFlagStatus is true and dataSourceMDSId exists', async () => {
const mockHttp = {
get: jest.fn().mockImplementation((url) => {
if (url === '/api/integrations/store') {
return Promise.resolve({
data: {
hits: [
{
dataSource: 'flint_test_default',
references: [{ id: 'test-mdsid', name: 'Test Integration', type: 'data-source' }],
},
{
dataSource: 'flint_test_default',
references: [
{ id: 'other-mdsid', name: 'Other Integration', type: 'data-source' },
],
},
],
},
});
} else {
return Promise.resolve({
allowedRoles: ['role1'],
description: 'Test description',
name: 'Test datasource',
connector: 'S3GLUE',
properties: {},
status: 'ACTIVE',
});
}
}),
};

const mockNotifications = {
toasts: {
addDanger: jest.fn(),
},
};

const mockSavedObjects = {
client: {
find: jest.fn().mockResolvedValue({
saved_objects: [
{
id: 'test-mdsid',
attributes: {
title: 'Test Data Source',
},
},
],
}),
},
};

(utils.getDataSourcesWithFields as jest.Mock).mockResolvedValue([
{
id: 'test-mdsid',
attributes: {
title: 'Test Data Source',
},
},
]);

renderComponent({
featureFlagStatus: true,
http: mockHttp,
notifications: mockNotifications,
savedObjects: mockSavedObjects,
});

await waitFor(() => expect(mockHttp.get).toHaveBeenCalledWith('/api/integrations/store'), {
timeout: 1000,
});

await waitFor(
() => {
const filteredIntegration = screen.queryByText('Configure Integrations');
expect(filteredIntegration).toBeInTheDocument();
},
{ timeout: 1000 }
);

expect(mockNotifications.toasts.addDanger).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ import {
EuiCard,
EuiAccordion,
} from '@elastic/eui';
import { ApplicationStart, HttpStart, NotificationsStart } from 'opensearch-dashboards/public';
import {
ApplicationStart,
HttpStart,
NotificationsStart,
SavedObjectsStart,
} from 'opensearch-dashboards/public';
import { useLocation, useParams } from 'react-router-dom';
import { escapeRegExp } from 'lodash';
import { DATACONNECTIONS_BASE } from '../../../constants';
Expand All @@ -46,7 +51,7 @@ import {
IntegrationInstancesSearchResult,
} from '../../../../framework/types';
import { INTEGRATIONS_BASE } from '../../../../framework/utils/shared';
import { isPluginInstalled } from '../../utils';
import { isPluginInstalled, getDataSourcesWithFields } from '../../utils';

interface DirectQueryDataConnectionDetailProps {
featureFlagStatus: boolean;
Expand All @@ -55,6 +60,7 @@ interface DirectQueryDataConnectionDetailProps {
application: ApplicationStart;
setBreadcrumbs: (breadcrumbs: any) => void;
useNewUX: boolean;
savedObjects: SavedObjectsStart;
}

export const DirectQueryDataConnectionDetail: React.FC<DirectQueryDataConnectionDetailProps> = ({
Expand All @@ -64,6 +70,7 @@ export const DirectQueryDataConnectionDetail: React.FC<DirectQueryDataConnection
application,
setBreadcrumbs,
useNewUX,
savedObjects,
}) => {
const [observabilityDashboardsExists, setObservabilityDashboardsExists] = useState(false);
const { dataSourceName } = useParams<{ dataSourceName: string }>();
Expand Down Expand Up @@ -117,24 +124,60 @@ export const DirectQueryDataConnectionDetail: React.FC<DirectQueryDataConnection
const [refreshIntegrationsFlag, setRefreshIntegrationsFlag] = useState(false);
const refreshInstances = () => setRefreshIntegrationsFlag((prev) => !prev);

const [clusterTitle, setDataSourceTitle] = useState<string | undefined>();
const fetchDataSources = async () => {
try {
const dataSources = await getDataSourcesWithFields(savedObjects.client, ['id', 'title']);

// Find the data source title based on the dataSourceMDSId
const foundDataSource = dataSources.find((ds: any) => ds.id === dataSourceMDSId);
if (foundDataSource) {
setDataSourceTitle(foundDataSource.attributes.title);
}
} catch (error) {
notifications.toasts.addDanger({
title: 'Failed to fetch data sources',
text: error.message,
});
}
};

useEffect(() => {
if (featureFlagStatus) {
fetchDataSources();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [featureFlagStatus, savedObjects, notifications, dataSourceMDSId]);

useEffect(() => {
const searchDataSourcePattern = new RegExp(
`flint_${escapeRegExp(datasourceDetails.name)}_default_.*`
);

const findIntegrations = async () => {
const result: { data: IntegrationInstancesSearchResult } = await http!.get(
INTEGRATIONS_BASE + `/store`
);

if (result.data?.hits) {
setDataSourceIntegrations(
result.data.hits.filter((res) => res.dataSource.match(searchDataSourcePattern))
let filteredIntegrations = result.data.hits.filter((res) =>
res.dataSource.match(searchDataSourcePattern)
);

if (featureFlagStatus && dataSourceMDSId !== null) {
filteredIntegrations = filteredIntegrations.filter((res) => {
return res.references && res.references.some((ref) => ref.id === dataSourceMDSId);
});
}

setDataSourceIntegrations(filteredIntegrations);
} else {
setDataSourceIntegrations([]);
}
};

findIntegrations();
}, [http, datasourceDetails.name, refreshIntegrationsFlag]);
}, [http, datasourceDetails.name, refreshIntegrationsFlag, featureFlagStatus, dataSourceMDSId]);

const [showIntegrationsFlyout, setShowIntegrationsFlyout] = useState(false);
const onclickIntegrationsCard = () => {
Expand All @@ -146,6 +189,8 @@ export const DirectQueryDataConnectionDetail: React.FC<DirectQueryDataConnection
datasourceType={datasourceDetails.connector}
datasourceName={datasourceDetails.name}
http={http}
selectedDataSourceId={dataSourceMDSId || ''}
selectedClusterName={clusterTitle}
/>
) : null;

Expand Down Expand Up @@ -189,7 +234,7 @@ export const DirectQueryDataConnectionDetail: React.FC<DirectQueryDataConnection
const DefaultDatasourceCards = () => {
return (
<EuiFlexGroup>
{!featureFlagStatus && observabilityDashboardsExists && (
{observabilityDashboardsExists && (
<EuiFlexItem>
<EuiCard
icon={<EuiIcon size="xxl" type="integrationGeneral" />}
Expand Down Expand Up @@ -402,21 +447,22 @@ export const DirectQueryDataConnectionDetail: React.FC<DirectQueryDataConnection
/>
),
},
!featureFlagStatus &&
observabilityDashboardsExists && {
id: 'installed_integrations',
name: 'Installed Integrations',
disabled: false,
content: (
<InstalledIntegrationsTable
integrations={dataSourceIntegrations}
datasourceType={datasourceDetails.connector}
datasourceName={datasourceDetails.name}
refreshInstances={refreshInstances}
http={http}
/>
),
},
observabilityDashboardsExists && {
id: 'installed_integrations',
name: 'Installed Integrations',
disabled: false,
content: (
<InstalledIntegrationsTable
integrations={dataSourceIntegrations}
datasourceType={datasourceDetails.connector}
datasourceName={datasourceDetails.name}
refreshInstances={refreshInstances}
http={http}
selectedDataSourceId={featureFlagStatus ? dataSourceMDSId ?? undefined : undefined}
selectedClusterName={clusterTitle}
/>
),
},
].filter(Boolean)
: [];

Expand Down
Loading

0 comments on commit a87ce21

Please sign in to comment.