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

add multi data source support #315

Merged
merged 4 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
"dashboard",
"opensearchUiShared"
],
"optionalPlugins": []
}
"optionalPlugins": ["dataSource", "dataSourceManagement"]
}
17 changes: 13 additions & 4 deletions public/apis/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,22 @@ interface GetAllInternalConnectorResponse {
}

export class Connector {
public getAll() {
return InnerHttpProvider.getHttp().get<GetAllConnectorResponse>(CONNECTOR_API_ENDPOINT);
public getAll({ dataSourceId }: { dataSourceId?: string }) {
return InnerHttpProvider.getHttp().get<GetAllConnectorResponse>(CONNECTOR_API_ENDPOINT, {
query: {
data_source_id: dataSourceId,
},
});
}

public getAllInternal() {
public getAllInternal({ dataSourceId }: { dataSourceId?: string }) {
return InnerHttpProvider.getHttp().get<GetAllInternalConnectorResponse>(
INTERNAL_CONNECTOR_API_ENDPOINT
INTERNAL_CONNECTOR_API_ENDPOINT,
{
query: {
data_source_id: dataSourceId,
},
}
);
}
}
7 changes: 5 additions & 2 deletions public/apis/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@ export class Model {
states?: MODEL_STATE[];
nameOrId?: string;
extraQuery?: Record<string, any>;
dataSourceId?: string;
}) {
const { extraQuery, ...restQuery } = query;
const { extraQuery, dataSourceId, ...restQuery } = query;
return InnerHttpProvider.getHttp().get<ModelSearchResponse>(MODEL_API_ENDPOINT, {
query: extraQuery ? { ...restQuery, extra_query: JSON.stringify(extraQuery) } : restQuery,
query: extraQuery
? { ...restQuery, extra_query: JSON.stringify(extraQuery), data_source_id: dataSourceId }
: { ...restQuery, data_source_id: dataSourceId },
});
}
}
9 changes: 7 additions & 2 deletions public/apis/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ export interface ModelDeploymentProfile {
}

export class Profile {
public getModel(modelId: string) {
public getModel(modelId: string, { dataSourceId }: { dataSourceId?: string }) {
return InnerHttpProvider.getHttp().get<ModelDeploymentProfile>(
`${DEPLOYED_MODEL_PROFILE_API_ENDPOINT}/${modelId}`
`${DEPLOYED_MODEL_PROFILE_API_ENDPOINT}/${modelId}`,
{
query: {
data_source_id: dataSourceId,
},
}
);
}
}
6 changes: 5 additions & 1 deletion public/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { APIProvider } from './apis/api_provider';
import { OpenSearchDashboardsContextProvider } from '../../../src/plugins/opensearch_dashboards_react/public';

export const renderApp = (
{ element, history, appBasePath }: AppMountParameters,
{ element, history, appBasePath, setHeaderActionMenu }: AppMountParameters,
services: MLServices
) => {
InnerHttpProvider.setHttp(services.http);
Expand All @@ -31,6 +31,10 @@ export const renderApp = (
chrome={services.chrome}
data={services.data}
uiSettingsClient={services.uiSettings}
savedObjects={services.savedObjects}
setActionMenu={setHeaderActionMenu}
dataSource={services.dataSource}
dataSourceManagement={services.dataSourceManagement}
/>
</services.i18n.Context>
</OpenSearchDashboardsContextProvider>
Expand Down
112 changes: 112 additions & 0 deletions public/components/__tests__/data_source_top_nav_menu.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useContext } from 'react';
import userEvent from '@testing-library/user-event';
import { render, screen, waitFor } from '../../../test/test_utils';
import { DataSourceTopNavMenu, DataSourceTopNavMenuProps } from '../data_source_top_nav_menu';
import { coreMock } from '../../../../../src/core/public/mocks';
import { DataSourceContext } from '../../contexts';

function setup(options: Partial<DataSourceTopNavMenuProps> = {}) {
const user = userEvent.setup({});
const coreStart = coreMock.createStart();
const DataSourceMenu = ({ componentConfig: { onSelectedDataSources } }) => (
<div>
<div>Data Source Menu</div>
<div>
<button
onClick={() => {
onSelectedDataSources([]);
}}
aria-label="invalidDataSource"
>
Invalid data source
</button>
<button
onClick={() => {
onSelectedDataSources([{ id: 'ds1', label: 'Data Source 1' }]);
}}
aria-label="validDataSource"
>
Valid data source
</button>
</div>
</div>
);

const DataSourceConsumer = () => {
const { selectedDataSourceOption } = useContext(DataSourceContext);

return (
<div>
<input
value={
selectedDataSourceOption === undefined
? 'undefined'
: JSON.stringify(selectedDataSourceOption)
}
aria-label="selectedDataSourceOption"
onChange={() => {}}
/>
</div>
);
};

const renderResult = render(
<>
<DataSourceTopNavMenu
notifications={coreStart.notifications}
savedObjects={coreStart.savedObjects}
dataSourceManagement={{
registerAuthenticationMethod: jest.fn(),
ui: {
DataSourceSelector: () => null,
getDataSourceMenu: () => DataSourceMenu,
},
}}
setActionMenu={jest.fn()}
{...options}
/>
<DataSourceConsumer />
</>
);
return { user, renderResult };
}

describe('<DataSourceTopNavMenu />', () => {
it('should not render data source menu when data source management not defined', () => {
setup({
dataSourceManagement: undefined,
});
expect(screen.queryByText('Data Source Menu')).not.toBeInTheDocument();
});

it('should render data source menu and data source context', () => {
setup();
expect(screen.getByText('Data Source Menu')).toBeInTheDocument();
expect(screen.getByLabelText('selectedDataSourceOption')).toHaveValue('null');
});

it('should set selected data source option to undefined', async () => {
const { user } = setup();
expect(screen.getByText('Data Source Menu')).toBeInTheDocument();
await user.click(screen.getByLabelText('invalidDataSource'));
await waitFor(() => {
expect(screen.getByLabelText('selectedDataSourceOption')).toHaveValue('undefined');
});
});

it('should set selected data source option to valid data source', async () => {
const { user } = setup();
expect(screen.getByText('Data Source Menu')).toBeInTheDocument();
await user.click(screen.getByLabelText('validDataSource'));
await waitFor(() => {
expect(screen.getByLabelText('selectedDataSourceOption')).toHaveValue(
JSON.stringify({ id: 'ds1', label: 'Data Source 1' })
);
});
});
});
72 changes: 53 additions & 19 deletions public/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@ import { EuiPage, EuiPageBody } from '@elastic/eui';
import { ROUTES } from '../../common/router';
import { routerPaths } from '../../common/router_paths';

import { CoreStart, IUiSettingsClient } from '../../../../src/core/public';
import {
CoreStart,
IUiSettingsClient,
MountPoint,
SavedObjectsStart,
} from '../../../../src/core/public';
import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public';
import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
import type { DataSourceManagementPluginSetup } from '../../../../src/plugins/data_source_management/public';
import type { DataSourcePluginSetup } from '../../../../src/plugins/data_source/public';
import { DataSourceContextProvider } from '../contexts/data_source_context';

import { GlobalBreadcrumbs } from './global_breadcrumbs';
import { DataSourceTopNavMenu } from './data_source_top_nav_menu';

interface MlCommonsPluginAppDeps {
basename: string;
Expand All @@ -24,6 +33,10 @@ interface MlCommonsPluginAppDeps {
chrome: CoreStart['chrome'];
data: DataPublicPluginStart;
uiSettingsClient: IUiSettingsClient;
savedObjects: SavedObjectsStart;
dataSource?: DataSourcePluginSetup;
dataSourceManagement?: DataSourceManagementPluginSetup;
setActionMenu: (menuMount: MountPoint | undefined) => void;
}

export interface ComponentsCommonProps {
Expand All @@ -38,27 +51,48 @@ export const MlCommonsPluginApp = ({
http,
chrome,
data,
dataSource,
dataSourceManagement,
savedObjects,
setActionMenu,
}: MlCommonsPluginAppDeps) => {
const dataSourceEnabled = !!dataSource;
return (
<I18nProvider>
<>
<EuiPage>
<EuiPageBody component="main">
<Switch>
{ROUTES.map(({ path, Component, exact }) => (
<Route
key={path}
path={path}
render={() => <Component http={http} notifications={notifications} data={data} />}
exact={exact ?? false}
/>
))}
<Redirect from={routerPaths.root} to={routerPaths.overview} />
</Switch>
</EuiPageBody>
</EuiPage>
<GlobalBreadcrumbs chrome={chrome} basename={basename} />
</>
<DataSourceContextProvider
initialValue={{
dataSourceEnabled,
}}
>
<>
<EuiPage>
<EuiPageBody component="main">
<Switch>
{ROUTES.map(({ path, Component, exact }) => (
<Route
key={path}
path={path}
render={() => (
<Component http={http} notifications={notifications} data={data} />
)}
exact={exact ?? false}
/>
))}
<Redirect from={routerPaths.root} to={routerPaths.overview} />
</Switch>
</EuiPageBody>
</EuiPage>
<GlobalBreadcrumbs chrome={chrome} basename={basename} />
{dataSourceEnabled && (
<DataSourceTopNavMenu
notifications={notifications}
dataSourceManagement={dataSourceManagement}
setActionMenu={setActionMenu}
savedObjects={savedObjects}
/>
)}
</>
</DataSourceContextProvider>
</I18nProvider>
);
};
60 changes: 60 additions & 0 deletions public/components/data_source_top_nav_menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useMemo, useContext, useCallback } from 'react';

import type { CoreStart, MountPoint, SavedObjectsStart } from '../../../../src/core/public';
import type {
DataSourceManagementPluginSetup,
DataSourceSelectableConfig,
} from '../../../../src/plugins/data_source_management/public';
import { DataSourceContext } from '../contexts/data_source_context';

export interface DataSourceTopNavMenuProps {
notifications: CoreStart['notifications'];
savedObjects: SavedObjectsStart;
dataSourceManagement?: DataSourceManagementPluginSetup;
setActionMenu: (menuMount: MountPoint | undefined) => void;
}

export const DataSourceTopNavMenu = ({
savedObjects,
notifications,
setActionMenu,
dataSourceManagement,
}: DataSourceTopNavMenuProps) => {
const DataSourceMenu = useMemo(() => dataSourceManagement?.ui.getDataSourceMenu(), [
dataSourceManagement,
]);
const { selectedDataSourceOption, setSelectedDataSourceOption } = useContext(DataSourceContext);
const activeOption = useMemo(() => (selectedDataSourceOption ? [selectedDataSourceOption] : []), [
selectedDataSourceOption,
]);

const handleDataSourcesSelected = useCallback<
DataSourceSelectableConfig['onSelectedDataSources']
>(
(dataSourceOptions) => {
setSelectedDataSourceOption(dataSourceOptions[0]);
},
[setSelectedDataSourceOption]
);

if (!DataSourceMenu) {
return null;
}
return (
<DataSourceMenu
componentType="DataSourceSelectable"
componentConfig={{
notifications,
savedObjects: savedObjects.client,
onSelectedDataSources: handleDataSourcesSelected,
activeOption,
}}
setMenuMountPoint={setActionMenu}
/>
);
};
Loading
Loading