Skip to content

Commit

Permalink
Merge branch 'main' into dev-import-api
Browse files Browse the repository at this point in the history
  • Loading branch information
yujin-emma committed Feb 5, 2024
2 parents d1f73e7 + 8e93c54 commit 798c7a1
Show file tree
Hide file tree
Showing 25 changed files with 493 additions and 86 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [Custom Branding] Relative URL should be allowed for logos ([#5572](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5572))
- [Discover] Enhanced the data source selector with added sorting functionality ([#5609](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/5609))
- [Multiple Datasource] Add datasource picker component and use it in devtools and tutorial page when multiple datasource is enabled ([#5756](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5756))
- [Multiple Datasource] Add datasource picker to import saved object flyout when multiple data source is enabled ([#5781](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5781))

### 🐛 Bug Fixes

Expand Down
4 changes: 2 additions & 2 deletions src/plugins/data_source_management/opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"ui": true,
"requiredPlugins": ["management", "dataSource", "indexPatternManagement"],
"optionalPlugins": [],
"requiredBundles": ["opensearchDashboardsReact"],
"extraPublicDirs": ["public/components/utils", "public/components/data_source_picker/data_source_picker"]
"requiredBundles": ["opensearchDashboardsReact", "dataSource"],
"extraPublicDirs": ["public/components/utils"]
}

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,42 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { ShallowWrapper, shallow } from 'enzyme';
import { ClusterSelector } from './cluster_selector';
import { SavedObjectsClientContract } from '../../../../../core/public';
import { notificationServiceMock } from '../../../../../core/public/mocks';
import React from 'react';

describe('ClusterSelector', () => {
let component: ShallowWrapper<any, Readonly<{}>, React.Component<{}, {}, any>>;

let client: SavedObjectsClientContract;
const { toasts } = notificationServiceMock.createStartContract();

beforeEach(() => {
client = {
find: jest.fn().mockResolvedValue([]),
} as any;
component = shallow(
<ClusterSelector
savedObjectsClient={client}
notifications={toasts}
onSelectedDataSource={jest.fn()}
disabled={false}
fullWidth={false}
/>
);
});

it('should render normally', () => {
expect(component).toMatchSnapshot();
expect(client.find).toBeCalledWith({
fields: ['id', 'description', 'title'],
perPage: 10000,
type: 'data-source',
});
expect(toasts.addWarning).toBeCalledTimes(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,44 @@
*/

import React from 'react';
import { getDataSources } from '../utils';
import { i18n } from '@osd/i18n';
import { EuiComboBox } from '@elastic/eui';
import { SavedObjectsClientContract, ToastsStart } from 'opensearch-dashboards/public';
import { getDataSources } from '../utils';

export const LocalCluster = {
export const LocalCluster: ClusterOption = {
label: i18n.translate('dataSource.localCluster', {
defaultMessage: 'Local cluster',
}),
id: '',
};

export class DataSourcePicker extends React.Component {
constructor(props) {
interface ClusterSelectorProps {
savedObjectsClient: SavedObjectsClientContract;
notifications: ToastsStart;
onSelectedDataSource: (clusterOption: ClusterOption[]) => void;
disabled: boolean;
fullWidth: boolean;
}

interface ClusterSelectorState {
clusterOptions: ClusterOption[];
selectedOption: ClusterOption[];
}

export interface ClusterOption {
label: string;
id: string;
}

export class ClusterSelector extends React.Component<ClusterSelectorProps, ClusterSelectorState> {
private _isMounted: boolean = false;

constructor(props: ClusterSelectorProps) {
super(props);

this.state = {
dataSources: [],
clusterOptions: [],
selectedOption: [LocalCluster],
};
}
Expand All @@ -34,17 +55,17 @@ export class DataSourcePicker extends React.Component {
getDataSources(this.props.savedObjectsClient)
.then((fetchedDataSources) => {
if (fetchedDataSources?.length) {
const dataSourceOptions = fetchedDataSources.map((dataSource) => ({
const clusterOptions = fetchedDataSources.map((dataSource) => ({
id: dataSource.id,
label: dataSource.title,
}));

dataSourceOptions.push(LocalCluster);
clusterOptions.push(LocalCluster);

if (!this._isMounted) return;
this.setState({
...this.state,
dataSources: dataSourceOptions,
clusterOptions,
});
}
})
Expand All @@ -68,21 +89,23 @@ export class DataSourcePicker extends React.Component {
render() {
return (
<EuiComboBox
aria-label={i18n.translate('dataSourceComboBoxAriaLabel', {
aria-label={i18n.translate('clusterSelectorComboBoxAriaLabel', {
defaultMessage: 'Select a data source',
})}
placeholder={i18n.translate('dataSourceComboBoxPlaceholder', {
placeholder={i18n.translate('clusterSelectorComboBoxPlaceholder', {
defaultMessage: 'Select a data source',
})}
singleSelection={{ asPlainText: true }}
options={this.state.dataSources}
options={this.state.clusterOptions}
selectedOptions={this.state.selectedOption}
onChange={(e) => this.onChange(e)}
prepend={i18n.translate('dataSourceComboBoxPrepend', {
prepend={i18n.translate('clusterSelectorComboBoxPrepend', {
defaultMessage: 'Data source',
})}
compressed
isDisabled={this.props.disabled}
fullWidth={this.props.fullWidth || false}
data-test-subj={'clusterSelectorComboBox'}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export { ClusterSelector } from './cluster_selector';
1 change: 1 addition & 0 deletions src/plugins/data_source_management/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export function plugin() {
return new DataSourceManagementPlugin();
}
export { DataSourceManagementPluginStart } from './types';
export { ClusterSelector } from './components/cluster_selector';
8 changes: 4 additions & 4 deletions src/plugins/dev_tools/public/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ import {
ScopedHistory,
} from 'src/core/public';

// eslint-disable-next-line @osd/eslint/no-restricted-paths
import { DataSourcePicker } from '../../data_source_management/public/components/data_source_picker/data_source_picker';
import { ClusterSelector } from '../../data_source_management/public';
import { DevToolApp } from './dev_tool';
import { DevToolsSetupDependencies } from './plugin';
import { addHelpMenuToAppChrome } from './utils/util';
Expand Down Expand Up @@ -131,12 +130,13 @@ function DevToolsWrapper({
</EuiToolTip>
))}
{dataSourceEnabled ? (
<div className="devAppDataSourcePicker">
<DataSourcePicker
<div className="devAppClusterSelector">
<ClusterSelector
savedObjectsClient={savedObjects.client}
notifications={toasts}
onSelectedDataSource={onChange}
disabled={!dataSourceEnabled}
fullWidth={false}
/>
</div>
) : null}
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/dev_tools/public/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
flex-grow: 1;
}

.devAppDataSourcePicker {
.devAppClusterSelector {
margin: 7px 8px 0 0;
min-width: 400px;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ import PropTypes from 'prop-types';
import { Synopsis } from './synopsis';
import { SampleDataSetCards } from './sample_data_set_cards';
import { getServices } from '../opensearch_dashboards_services';
// eslint-disable-next-line @osd/eslint/no-restricted-paths
import { DataSourcePicker } from '../../../../data_source_management/public/components/data_source_picker/data_source_picker';

import {
EuiPage,
Expand All @@ -53,6 +51,7 @@ import {
import { getTutorials } from '../load_tutorials';
import { injectI18n, FormattedMessage } from '@osd/i18n/react';
import { i18n } from '@osd/i18n';
import { ClusterSelector } from '../../../../data_source_management/public';

const ALL_TAB_ID = 'all';
const SAMPLE_DATA_TAB_ID = 'sampleData';
Expand Down Expand Up @@ -228,8 +227,8 @@ class TutorialDirectoryUi extends React.Component {
const { isDataSourceEnabled } = this.state;

return isDataSourceEnabled ? (
<div className="sampledataSourcePicker">
<DataSourcePicker
<div className="sampleDataClusterSelector">
<ClusterSelector
savedObjectsClient={getServices().savedObjectsClient}
notifications={getServices().toastNotifications}
onSelectedDataSource={this.onSelectedDataSourceChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
"discover",
"home",
"visBuilder",
"visAugmenter"
"visAugmenter",
"dataSource"
],
"extraPublicDirs": ["public/lib"],
"requiredBundles": ["opensearchDashboardsReact", "home"]
"requiredBundles": ["opensearchDashboardsReact", "home", "dataSourceManagement"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,15 @@ interface ImportResponse {
export async function importFile(
http: HttpStart,
file: File,
{ createNewCopies, overwrite }: ImportMode
{ createNewCopies, overwrite }: ImportMode,
selectedDataSourceId?: string
) {
const formData = new FormData();
formData.append('file', file);
const query = createNewCopies ? { createNewCopies } : { overwrite };
if (selectedDataSourceId) {
query.dataSourceId = selectedDataSourceId;
}
return await http.post<ImportResponse>('/api/saved_objects/_import', {
body: formData,
headers: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,16 @@ async function callResolveImportErrorsApi(
http: HttpStart,
file: File,
retries: any,
createNewCopies: boolean
createNewCopies: boolean,
selectedDataSourceId?: string
): Promise<SavedObjectsImportResponse> {
const formData = new FormData();
formData.append('file', file);
formData.append('retries', JSON.stringify(retries));
const query = createNewCopies ? { createNewCopies } : {};
if (selectedDataSourceId) {
query.dataSourceId = selectedDataSourceId;
}
return http.post<any>('/api/saved_objects/_resolve_import_errors', {
headers: {
// Important to be undefined, it forces proper headers to be set for FormData
Expand Down Expand Up @@ -167,6 +171,7 @@ export async function resolveImportErrors({
http,
getConflictResolutions,
state,
selectedDataSourceId,
}: {
http: HttpStart;
getConflictResolutions: (
Expand All @@ -180,6 +185,7 @@ export async function resolveImportErrors({
file?: File;
importMode: { createNewCopies: boolean; overwrite: boolean };
};
selectedDataSourceId: string;
}) {
const retryDecisionCache = new Map<string, RetryDecision>();
const replaceReferencesCache = new Map<string, Reference[]>();
Expand Down Expand Up @@ -264,7 +270,13 @@ export async function resolveImportErrors({
}

// Call API
const response = await callResolveImportErrorsApi(http, file!, retries, createNewCopies);
const response = await callResolveImportErrorsApi(
http,
file!,
retries,
createNewCopies,
selectedDataSourceId
);
importCount = response.successCount; // reset the success count since we retry all successful results each time
failedImports = [];
for (const { error, ...obj } of response.errors || []) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ interface MountParams {
core: CoreSetup<StartDependencies, SavedObjectsManagementPluginStart>;
serviceRegistry: ISavedObjectsManagementServiceRegistry;
mountParams: ManagementAppMountParams;
dataSourceEnabled: boolean;
}

let allowedObjectTypes: string[] | undefined;
Expand All @@ -58,6 +59,7 @@ export const mountManagementSection = async ({
core,
mountParams,
serviceRegistry,
dataSourceEnabled,
}: MountParams) => {
const [coreStart, { data, uiActions }, pluginStart] = await core.getStartServices();
const { element, history, setBreadcrumbs } = mountParams;
Expand Down Expand Up @@ -108,6 +110,7 @@ export const mountManagementSection = async ({
namespaceRegistry={pluginStart.namespaces}
allowedTypes={allowedObjectTypes}
setBreadcrumbs={setBreadcrumbs}
dataSourceEnabled={dataSourceEnabled}
/>
</Suspense>
</RedirectToHomeIfUnauthorized>
Expand Down
Loading

0 comments on commit 798c7a1

Please sign in to comment.