Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Workspace] Integrate workspace front end with data connection type saved object #8013

Merged
merged 12 commits into from
Sep 20, 2024
Merged
2 changes: 2 additions & 0 deletions changelogs/fragments/8013.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
feat:
- Integrate workspace with data connections in front end ([#8013](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8013))
2 changes: 1 addition & 1 deletion src/plugins/data_source/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
10 changes: 10 additions & 0 deletions src/plugins/workspace/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -13,6 +15,14 @@ export type DataSource = Pick<
id: string;
};

export type DataConnection = Pick<DataConnectionSavedObjectAttributes, 'title'> & {
type: string;
id: string;
connectionType: DataConnectionType;
description?: string;
title: string;
};

export enum DataSourceConnectionType {
OpenSearchConnection,
DirectQueryConnection,
Expand Down
9 changes: 9 additions & 0 deletions src/plugins/workspace/public/assets/cloudwatch_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/plugins/workspace/public/assets/security_lake_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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();
Expand Down Expand Up @@ -243,6 +263,7 @@ describe('WorkspaceCreator', () => {
}),
{
dataSources: [],
dataConnections: [],
permissions: {
library_write: { users: ['%me%'] },
write: { users: ['%me%'] },
Expand Down Expand Up @@ -318,6 +339,7 @@ describe('WorkspaceCreator', () => {
name: 'test workspace name',
}),
{
dataConnections: [],
dataSources: [],
permissions: {
write: {
Expand Down Expand Up @@ -375,6 +397,7 @@ describe('WorkspaceCreator', () => {
name: 'test workspace name',
}),
{
dataConnections: [],
dataSources: ['id1'],
permissions: {
library_write: {
Expand All @@ -392,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(
<WorkspaceCreator isDashboardAdmin={true} />
);

// 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(
() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,16 @@ export const WorkspaceCreator = (props: WorkspaceCreatorProps) => {
.map(({ id }) => {
return id;
});
const selectedDataConnectionIds = (selectedDataSourceConnections ?? [])
.filter(
({ connectionType }) => connectionType === DataSourceConnectionType.DataConnection
)
.map(({ id }) => {
return id;
});
result = await workspaceClient.create(attributes, {
dataSources: selectedDataSourceIds,
dataConnections: selectedDataConnectionIds,
permissions: convertPermissionSettingsToPermissions(permissionSettings),
});
if (result?.success) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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',
},
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +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 { ConnectionTypeIcon } from '../workspace_form';

const ConnectionIcon = ({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo this could be cleaned up with using the data structures https://github.com/opensearch-project/OpenSearch-Dashboards/blob/main/src/plugins/data/common/datasets/types.ts#L75.

where we have the information about the icon and type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will take a look.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the interface of this data structure and haven't seen any related integration with data-connection type object. I suppose this is still in progress?

connection: { connectionType, type },
Expand All @@ -42,9 +42,13 @@ const ConnectionIcon = ({
if (connectionType === DataSourceConnectionType.OpenSearchConnection) {
return <EuiIcon type={logos.Mark.url} />;
}
if (connectionType === DataSourceConnectionType.DirectQueryConnection) {
return <DirectQueryConnectionIcon type={type} />;
if (
connectionType === DataSourceConnectionType.DirectQueryConnection ||
connectionType === DataSourceConnectionType.DataConnection
) {
return <ConnectionTypeIcon type={type} />;
}

return null;
};

Expand Down Expand Up @@ -143,6 +147,14 @@ const convertConnectionsToOptions = ({
) {
return [];
}

if (connection.connectionType === DataSourceConnectionType.DataConnection) {
if (showDirectQueryConnections) {
return [connection];
}
return [];
}

if (showDirectQueryConnections) {
if (!connection.relatedConnections || connection.relatedConnections.length === 0) {
return [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.connectionType === DataSourceConnectionType.DataConnection ||
(dsc?.relatedConnections && dsc.relatedConnections?.length > 0)
);
}, [connectionType, dataSourceConnections]);

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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(<ConnectionTypeIcon />)).toMatchSnapshot();
expect(mount(<ConnectionTypeIcon type="Amazon S3" />)).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -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 <EuiIcon type={s3Logo} />;
case 'Prometheus':
return <EuiIcon type={prometheusLogo} />;
case DataConnectionType.CloudWatch:
return <EuiIcon type={cloudWatchLogo} />;

Check warning on line 23 in src/plugins/workspace/public/components/workspace_form/connection_type_icon.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/workspace/public/components/workspace_form/connection_type_icon.tsx#L23

Added line #L23 was not covered by tests
case DataConnectionType.SecurityLake:
return <EuiIcon type={securityLakeLogo} />;
default:
return null;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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}`);
Expand Down Expand Up @@ -167,7 +171,7 @@ export const DataSourceConnectionTable = forwardRef<
<EuiButtonEmpty
data-test-subj={`workspace-detail-dataSources-table-dqc-${record.id}-related-button`}
size="xs"
flush="right"
flush="left"
color="text"
onClick={() => togglePopover(record.id)}
>
Expand All @@ -191,7 +195,7 @@ export const DataSourceConnectionTable = forwardRef<
key={item.id}
size="xs"
label={item.name}
icon={<DirectQueryConnectionIcon type={item.type} />}
icon={<ConnectionTypeIcon type={item.type} />}
style={{ maxHeight: '30px' }}
/>
))}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +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 { ConnectionTypeIcon } from './connection_type_icon';
export { DataSourceConnectionTable } from './data_source_connection_table';

export { WorkspaceFormSubmitData, WorkspaceFormProps, WorkspaceFormDataState } from './types';
Expand Down
Loading
Loading