From 09d8b50c039d83b189a1a4f4f7019a9f3f289c26 Mon Sep 17 00:00:00 2001 From: Manideep Pabba <109986843+mpabba3003@users.noreply.github.com> Date: Thu, 29 Sep 2022 16:56:48 -0500 Subject: [PATCH] [Backport 2.x] MultiDataSource feature merge (#2334) (#2409) Signed-off-by: mpabba3003 Co-authored-by: Kristen Tian <105667444+kristenTian@users.noreply.github.com> --- .../fetcher/index_patterns_fetcher.ts | 6 +- .../field_capabilities/field_capabilities.ts | 4 +- .../fetcher/lib/opensearch_api.ts | 31 +- .../fetcher/lib/resolve_time_pattern.ts | 5 +- .../cryptography/cryptography_client.test.ts | 117 ++++++++ .../cryptography/cryptography_client.ts | 70 +++++ .../data_source/server/cryptography/index.ts | 6 + .../public/components/text_content/index.ts | 6 + .../components/text_content/text_content.ts | 283 ++++++++++++++++++ .../opensearch_dashboards.json | 2 +- .../header/__snapshots__/header.test.tsx.snap | 238 +++++++++++++++ .../components/header/header.scss | 3 + .../lib/get_indices.test.ts | 1 + 13 files changed, 763 insertions(+), 9 deletions(-) create mode 100644 src/plugins/data_source/server/cryptography/cryptography_client.test.ts create mode 100644 src/plugins/data_source/server/cryptography/cryptography_client.ts create mode 100644 src/plugins/data_source/server/cryptography/index.ts create mode 100644 src/plugins/data_source_management/public/components/text_content/index.ts create mode 100644 src/plugins/data_source_management/public/components/text_content/text_content.ts create mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/__snapshots__/header.test.tsx.snap create mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/header.scss diff --git a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts index 9144e505133c..a53e06cdea0a 100644 --- a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts +++ b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts @@ -28,7 +28,7 @@ * under the License. */ -import { LegacyAPICaller } from 'opensearch-dashboards/server'; +import { LegacyAPICaller, OpenSearchClient } from 'opensearch-dashboards/server'; import { getFieldCapabilities, resolveTimePattern, createNoMatchingIndicesError } from './lib'; @@ -48,9 +48,9 @@ interface FieldSubType { } export class IndexPatternsFetcher { - private _callDataCluster: LegacyAPICaller; + private _callDataCluster: LegacyAPICaller | OpenSearchClient; - constructor(callDataCluster: LegacyAPICaller) { + constructor(callDataCluster: LegacyAPICaller | OpenSearchClient) { this._callDataCluster = callDataCluster; } diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts index 0aee1222a361..57d59ce5a486 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts @@ -30,7 +30,7 @@ import { defaults, keyBy, sortBy } from 'lodash'; -import { LegacyAPICaller } from 'opensearch-dashboards/server'; +import { LegacyAPICaller, OpenSearchClient } from 'opensearch-dashboards/server'; import { callFieldCapsApi } from '../opensearch_api'; import { FieldCapsResponse, readFieldCapsResponse } from './field_caps_response'; import { mergeOverrides } from './overrides'; @@ -47,7 +47,7 @@ import { FieldDescriptor } from '../../index_patterns_fetcher'; * @return {Promise>} */ export async function getFieldCapabilities( - callCluster: LegacyAPICaller, + callCluster: LegacyAPICaller | OpenSearchClient, indices: string | string[] = [], metaFields: string[] = [], fieldCapsOptions?: { allowNoIndices: boolean } diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/opensearch_api.ts b/src/plugins/data/server/index_patterns/fetcher/lib/opensearch_api.ts index 0a33e51b1018..5fcbb01ce07a 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/opensearch_api.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/opensearch_api.ts @@ -57,10 +57,23 @@ export interface IndexAliasResponse { * @return {Promise} */ export async function callIndexAliasApi( - callCluster: LegacyAPICaller, + callCluster: LegacyAPICaller | OpenSearchClient, indices: string[] | string ): Promise { try { + // This approach of identify between OpenSearchClient vs LegacyAPICaller + // will be deprecated after support data client with legacy client + // https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2133 + if ('transport' in callCluster) { + return ( + await callCluster.indices.getAlias({ + index: indices, + ignore_unavailable: true, + allow_no_indices: true, + }) + ).body as IndicesAliasResponse; + } + return (await callCluster('indices.getAlias', { index: indices, ignoreUnavailable: true, @@ -84,11 +97,25 @@ export async function callIndexAliasApi( * @return {Promise} */ export async function callFieldCapsApi( - callCluster: LegacyAPICaller, + callCluster: LegacyAPICaller | OpenSearchClient, indices: string[] | string, fieldCapsOptions: { allowNoIndices: boolean } = { allowNoIndices: false } ) { try { + // This approach of identify between OpenSearchClient vs LegacyAPICaller + // will be deprecated after support data client with legacy client + // https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2133 + if ('transport' in callCluster) { + return ( + await callCluster.fieldCaps({ + index: indices, + fields: '*', + ignore_unavailable: true, + allow_no_indices: fieldCapsOptions.allowNoIndices, + }) + ).body as FieldCapsResponse; + } + return (await callCluster('fieldCaps', { index: indices, fields: '*', diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts index c1ef0074a1d6..7b19ff78646f 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts @@ -47,7 +47,10 @@ import { callIndexAliasApi, IndicesAliasResponse } from './opensearch_api'; * and the indices that actually match the time * pattern (matches); */ -export async function resolveTimePattern(callCluster: LegacyAPICaller, timePattern: string) { +export async function resolveTimePattern( + callCluster: LegacyAPICaller | OpenSearchClient, + timePattern: string +) { const aliases = await callIndexAliasApi(callCluster, timePatternToWildcard(timePattern)); const allIndexDetails = chain(aliases) diff --git a/src/plugins/data_source/server/cryptography/cryptography_client.test.ts b/src/plugins/data_source/server/cryptography/cryptography_client.test.ts new file mode 100644 index 000000000000..1f8d2596a3c4 --- /dev/null +++ b/src/plugins/data_source/server/cryptography/cryptography_client.test.ts @@ -0,0 +1,117 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { CryptographyClient } from './cryptography_client'; +import { randomBytes } from 'crypto'; + +const dummyWrappingKeyName = 'dummy_wrapping_key_name'; +const dummyWrappingKeyNamespace = 'dummy_wrapping_key_namespace'; + +test('Invalid wrapping key size throws error', () => { + const dummyRandomBytes = [...randomBytes(31)]; + const expectedErrorMsg = `Wrapping key size shoule be 32 bytes, as used in envelope encryption. Current wrapping key size: '${dummyRandomBytes.length}' bytes`; + expect(() => { + new CryptographyClient(dummyWrappingKeyName, dummyWrappingKeyNamespace, dummyRandomBytes); + }).toThrowError(new Error(expectedErrorMsg)); +}); + +describe('Test encrpyt and decrypt module', () => { + const dummyPlainText = 'dummy'; + const dummyNumArray1 = [...randomBytes(32)]; + const dummyNumArray2 = [...randomBytes(32)]; + + describe('Positive test cases', () => { + test('Encrypt and Decrypt with same in memory keyring', async () => { + const cryptographyClient = new CryptographyClient( + dummyWrappingKeyName, + dummyWrappingKeyNamespace, + dummyNumArray1 + ); + const encrypted = await cryptographyClient.encryptAndEncode(dummyPlainText); + const outputText = await cryptographyClient.decodeAndDecrypt(encrypted); + expect(outputText).toBe(dummyPlainText); + }); + test('Encrypt and Decrypt with two different keyrings with exact same identifiers', async () => { + const cryptographyClient1 = new CryptographyClient( + dummyWrappingKeyName, + dummyWrappingKeyNamespace, + dummyNumArray1 + ); + const encrypted = await cryptographyClient1.encryptAndEncode(dummyPlainText); + + const cryptographyClient2 = new CryptographyClient( + dummyWrappingKeyName, + dummyWrappingKeyNamespace, + dummyNumArray1 + ); + const outputText = await cryptographyClient2.decodeAndDecrypt(encrypted); + expect(cryptographyClient1 === cryptographyClient2).toBeFalsy(); + expect(outputText).toBe(dummyPlainText); + }); + }); + + describe('Negative test cases', () => { + const defaultWrappingKeyName = 'changeme'; + const defaultWrappingKeyNamespace = 'changeme'; + const expectedErrorMsg = 'unencryptedDataKey has not been set'; + test('Encrypt and Decrypt with different key names', async () => { + const cryptographyClient1 = new CryptographyClient( + dummyWrappingKeyName, + dummyWrappingKeyNamespace, + dummyNumArray1 + ); + const encrypted = await cryptographyClient1.encryptAndEncode(dummyPlainText); + + const cryptographyClient2 = new CryptographyClient( + defaultWrappingKeyName, + dummyWrappingKeyNamespace, + dummyNumArray1 + ); + try { + await cryptographyClient2.decodeAndDecrypt(encrypted); + } catch (error) { + expect(error.message).toMatch(expectedErrorMsg); + } + }); + test('Encrypt and Decrypt with different key namespaces', async () => { + const cryptographyClient1 = new CryptographyClient( + dummyWrappingKeyName, + dummyWrappingKeyNamespace, + dummyNumArray1 + ); + const encrypted = await cryptographyClient1.encryptAndEncode(dummyPlainText); + + const cryptographyClient2 = new CryptographyClient( + dummyWrappingKeyName, + defaultWrappingKeyNamespace, + dummyNumArray1 + ); + try { + await cryptographyClient2.decodeAndDecrypt(encrypted); + } catch (error) { + expect(error.message).toMatch(expectedErrorMsg); + } + }); + test('Encrypt and Decrypt with different wrapping keys', async () => { + const cryptographyClient1 = new CryptographyClient( + dummyWrappingKeyName, + dummyWrappingKeyNamespace, + dummyNumArray1 + ); + const encrypted = await cryptographyClient1.encryptAndEncode(dummyPlainText); + + const cryptographyClient2 = new CryptographyClient( + dummyWrappingKeyName, + dummyWrappingKeyNamespace, + dummyNumArray2 + ); + try { + await cryptographyClient2.decodeAndDecrypt(encrypted); + } catch (error) { + expect(error.message).toMatch(expectedErrorMsg); + } + }); + }); +}); diff --git a/src/plugins/data_source/server/cryptography/cryptography_client.ts b/src/plugins/data_source/server/cryptography/cryptography_client.ts new file mode 100644 index 000000000000..f5968ae13adb --- /dev/null +++ b/src/plugins/data_source/server/cryptography/cryptography_client.ts @@ -0,0 +1,70 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + buildClient, + CommitmentPolicy, + RawAesKeyringNode, + RawAesWrappingSuiteIdentifier, +} from '@aws-crypto/client-node'; + +export const ENCODING_STRATEGY: BufferEncoding = 'base64'; +export const WRAPPING_KEY_SIZE: number = 32; + +export class CryptographyClient { + private readonly commitmentPolicy = CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT; + private readonly wrappingSuite = RawAesWrappingSuiteIdentifier.AES256_GCM_IV12_TAG16_NO_PADDING; + + private keyring: RawAesKeyringNode; + + private readonly encrypt: Function; + private readonly decrypt: Function; + + /** + * @param {string} wrappingKeyName name value to identify the AES key in a keyring + * @param {string} wrappingKeyNamespace namespace value to identify the AES key in a keyring, + * @param {number[]} wrappingKey 32 Bytes raw wrapping key used to perform envelope encryption + */ + constructor(wrappingKeyName: string, wrappingKeyNamespace: string, wrappingKey: number[]) { + if (wrappingKey.length !== WRAPPING_KEY_SIZE) { + const wrappingKeySizeMismatchMsg = `Wrapping key size shoule be 32 bytes, as used in envelope encryption. Current wrapping key size: '${wrappingKey.length}' bytes`; + throw new Error(wrappingKeySizeMismatchMsg); + } + + // Create raw AES keyring + this.keyring = new RawAesKeyringNode({ + keyName: wrappingKeyName, + keyNamespace: wrappingKeyNamespace, + unencryptedMasterKey: new Uint8Array(wrappingKey), + wrappingSuite: this.wrappingSuite, + }); + + // Destructuring encrypt and decrypt functions from client + const { encrypt, decrypt } = buildClient(this.commitmentPolicy); + + this.encrypt = encrypt; + this.decrypt = decrypt; + } + + /** + * Input text content and output encrypted string encoded with ENCODING_STRATEGY + * @param {string} plainText + * @returns {Promise} + */ + public async encryptAndEncode(plainText: string): Promise { + const result = await this.encrypt(this.keyring, plainText); + return result.result.toString(ENCODING_STRATEGY); + } + + /** + * Input encrypted content and output decrypted string + * @param {string} encrypted + * @returns {Promise} + */ + public async decodeAndDecrypt(encrypted: string): Promise { + const result = await this.decrypt(this.keyring, Buffer.from(encrypted, ENCODING_STRATEGY)); + return result.plaintext.toString(); + } +} diff --git a/src/plugins/data_source/server/cryptography/index.ts b/src/plugins/data_source/server/cryptography/index.ts new file mode 100644 index 000000000000..857fa691bddf --- /dev/null +++ b/src/plugins/data_source/server/cryptography/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { CryptographyClient } from './cryptography_client'; diff --git a/src/plugins/data_source_management/public/components/text_content/index.ts b/src/plugins/data_source_management/public/components/text_content/index.ts new file mode 100644 index 000000000000..6caa2514600b --- /dev/null +++ b/src/plugins/data_source_management/public/components/text_content/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './text_content'; diff --git a/src/plugins/data_source_management/public/components/text_content/text_content.ts b/src/plugins/data_source_management/public/components/text_content/text_content.ts new file mode 100644 index 000000000000..ee6bca828eb0 --- /dev/null +++ b/src/plugins/data_source_management/public/components/text_content/text_content.ts @@ -0,0 +1,283 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; + +/* Generic */ +export const cancelText = i18n.translate('cancel', { + defaultMessage: 'Cancel', +}); + +export const deleteText = i18n.translate('delete', { + defaultMessage: 'Delete', +}); + +export const titleText = i18n.translate('title', { + defaultMessage: 'Title', +}); + +export const descriptionText = i18n.translate('description', { + defaultMessage: 'Description', +}); + +export const usernameText = i18n.translate('username', { + defaultMessage: 'Username', +}); + +export const passwordText = i18n.translate('password', { + defaultMessage: 'Password', +}); + +/* Datasource listing page */ +export const dsListingAriaRegion = i18n.translate( + 'dataSourcesManagement.createDataSourcesLiveRegionAriaLabel', + { + defaultMessage: 'Data Sources', + } +); +export const dsListingTitle = i18n.translate('dataSourcesManagement.dataSourcesTable.title', { + defaultMessage: 'Data Sources', +}); + +export const dsListingDescription = i18n.translate( + 'dataSourcesManagement.dataSourcesTable.description', + { + defaultMessage: + 'Create and manage the data sources that help you retrieve your data from multiple Elasticsearch clusters', + } +); + +export const dsListingPageTitle = i18n.translate( + 'dataSourcesManagement.dataSourcesTable.dataSourcesTitle', + { + defaultMessage: 'Data Sources', + } +); + +export const dsListingDeleteDataSourceTitle = i18n.translate( + 'dataSourcesManagement.dataSourcesTable.deleteTitle', + { + defaultMessage: 'Delete Data Source connection(s) permanently?', + } +); + +export const dsListingDeleteDataSourceDescription = i18n.translate( + 'dataSourcesManagement.dataSourcesTable.deleteDescription', + { + defaultMessage: + 'This will delete data source connections(s) and all Index Patterns using this credential will be invalid for access.', + } +); + +export const dsListingDeleteDataSourceConfirmation = i18n.translate( + 'dataSourcesManagement.dataSourcesTable.deleteConfirmation', + { + defaultMessage: 'To confirm deletion, click delete button.', + } +); + +export const dsListingDeleteDataSourceWarning = i18n.translate( + 'dataSourcesManagement.dataSourcesTable.deleteWarning', + { + defaultMessage: 'Note: this action is irrevocable!', + } +); + +/* CREATE DATA SOURCE */ +export const createDataSourceHeader = i18n.translate( + 'dataSourcesManagement.createDataSourceHeader', + { + defaultMessage: 'Create data source connection', + } +); +export const createDataSourceDescriptionPlaceholder = i18n.translate( + 'dataSourcesManagement.createDataSource.descriptionPlaceholder', + { + defaultMessage: 'Description of the data source', + } +); +export const createDataSourceEndpointURL = i18n.translate( + 'dataSourcesManagement.createDataSource.endpointURL', + { + defaultMessage: 'Endpoint URL', + } +); +export const createDataSourceEndpointPlaceholder = i18n.translate( + 'dataSourcesManagement.createDataSource.endpointPlaceholder', + { + defaultMessage: 'The connection URL', + } +); +export const createDataSourceUsernamePlaceholder = i18n.translate( + 'dataSourcesManagement.createDataSource.usernamePlaceholder', + { + defaultMessage: 'Username to connect to data source', + } +); +export const createDataSourcePasswordPlaceholder = i18n.translate( + 'dataSourcesManagement.createDataSource.passwordPlaceholder', + { + defaultMessage: 'Password to connect to data source', + } +); +export const createDataSourceCredentialSource = i18n.translate( + 'dataSourcesManagement.createDataSource.credentialSource', + { + defaultMessage: 'Credential Source', + } +); + +/* Edit data source */ +export const dataSourceNotFound = i18n.translate( + 'dataSourcesManagement.editDataSource.dataSourceNotFound', + { + defaultMessage: 'Data Source not found!', + } +); +export const deleteThisDataSource = i18n.translate( + 'dataSourcesManagement.editDataSource.deleteThisDataSource', + { + defaultMessage: 'Delete this Data Source', + } +); +export const oldPasswordText = i18n.translate('dataSourcesManagement.editDataSource.oldPassword', { + defaultMessage: 'Old password', +}); +export const newPasswordText = i18n.translate('dataSourcesManagement.editDataSource.newPassword', { + defaultMessage: 'New password', +}); +export const confirmNewPasswordText = i18n.translate( + 'dataSourcesManagement.editDataSource.confirmNewPassword', + { + defaultMessage: 'Confirm new password', + } +); +export const updatePasswordText = i18n.translate( + 'dataSourcesManagement.editDataSource.updatePasswordText', + { + defaultMessage: 'Update password', + } +); +export const connectionDetailsText = i18n.translate( + 'dataSourcesManagement.editDataSource.connectionDetailsText', + { + defaultMessage: 'Connection Details', + } +); +export const objectDetailsText = i18n.translate( + 'dataSourcesManagement.editDataSource.objectDetailsText', + { + defaultMessage: 'Object Details', + } +); +export const objectDetailsDescription = i18n.translate( + 'dataSourcesManagement.editDataSource.objectDetailsDescription', + { + defaultMessage: + 'This connection information is used for reference in tables and when adding to a data source connection', + } +); +export const authenticationMethodTitle = i18n.translate( + 'dataSourcesManagement.editDataSource.authenticationMethodTitle', + { + defaultMessage: 'Authentication Method', + } +); +export const authenticationTitle = i18n.translate( + 'dataSourcesManagement.editDataSource.authenticationTitle', + { + defaultMessage: 'Authentication', + } +); +export const authenticationDetailsText = i18n.translate( + 'dataSourcesManagement.editDataSource.authenticationDetailsText', + { + defaultMessage: 'Authentication Details', + } +); +export const authenticationDetailsDescription = i18n.translate( + 'dataSourcesManagement.editDataSource.authenticationDetailsDescription', + { + defaultMessage: 'Modify these to update the authentication type and associated details', + } +); +export const endpointTitle = i18n.translate('dataSourcesManagement.editDataSource.endpointTitle', { + defaultMessage: 'Endpoint', +}); +export const endpointDescription = i18n.translate( + 'dataSourcesManagement.editDataSource.endpointDescription', + { + defaultMessage: + 'This connection information is used for reference in tables and when adding to a data source connection', + } +); + +export const cancelChangesText = i18n.translate( + 'dataSourcesManagement.editDataSource.cancelButtonLabel', + { + defaultMessage: 'Cancel changes', + } +); +export const saveChangesText = i18n.translate( + 'dataSourcesManagement.editDataSource.saveButtonLabel', + { + defaultMessage: 'Save changes', + } +); + +export const validationErrorTooltipText = i18n.translate( + 'dataSourcesManagement.editDataSource.saveButtonTooltipWithInvalidChanges', + { + defaultMessage: 'Fix invalid settings before saving.', + } +); + +/* Password validation */ + +export const dataSourceValidationOldPasswordEmpty = i18n.translate( + 'dataSourcesManagement.validation.oldPasswordEmpty', + { + defaultMessage: 'Old password cannot be empty', + } +); +export const dataSourceValidationNewPasswordEmpty = i18n.translate( + 'dataSourcesManagement.validation.newPasswordEmpty', + { + defaultMessage: 'New password cannot be empty', + } +); +export const dataSourceValidationNoPasswordMatch = i18n.translate( + 'dataSourcesManagement.validation.noPasswordMatch', + { + defaultMessage: 'Passwords do not match', + } +); + +/* Create/Edit validation */ + +export const dataSourceValidationTitleEmpty = i18n.translate( + 'dataSourcesManagement.validation.titleEmpty', + { + defaultMessage: 'Title must not be empty', + } +); +export const dataSourceValidationEndpointNotValid = i18n.translate( + 'dataSourcesManagement.validation.endpointNotValid', + { + defaultMessage: 'Endpoint is not valid', + } +); +export const dataSourceValidationUsernameEmpty = i18n.translate( + 'dataSourcesManagement.validation.usernameEmpty', + { + defaultMessage: 'Username should not be empty', + } +); +export const dataSourceValidationPasswordEmpty = i18n.translate( + 'dataSourcesManagement.validation.passwordEmpty', + { + defaultMessage: 'Password should not be empty', + } +); diff --git a/src/plugins/index_pattern_management/opensearch_dashboards.json b/src/plugins/index_pattern_management/opensearch_dashboards.json index 611f122c8c16..1efd05cb1a49 100644 --- a/src/plugins/index_pattern_management/opensearch_dashboards.json +++ b/src/plugins/index_pattern_management/opensearch_dashboards.json @@ -5,5 +5,5 @@ "ui": true, "optionalPlugins": ["dataSource"], "requiredPlugins": ["management", "data", "urlForwarding"], - "requiredBundles": ["opensearchDashboardsReact", "opensearchDashboardsUtils"] + "requiredBundles": ["opensearchDashboardsReact", "opensearchDashboardsUtils", "savedObjects"] } diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/__snapshots__/header.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/__snapshots__/header.test.tsx.snap new file mode 100644 index 000000000000..e15b9428b45c --- /dev/null +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/__snapshots__/header.test.tsx.snap @@ -0,0 +1,238 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Header should render data source finder when choose to use data source 1`] = ` +
+ +

+ +

+
+ + + + + + } + onChange={[Function]} + /> + + + + + + } + onChange={[Function]} + /> + + + + + + + + + + + + + + + +
+`; + +exports[`Header should render normally 1`] = ` +
+ +

+ +

+
+ + + + + + } + onChange={[Function]} + /> + + + + + + } + onChange={[Function]} + /> + + + + + + + + + + + +
+`; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/header.scss b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/header.scss new file mode 100644 index 000000000000..048ae2981c05 --- /dev/null +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/header.scss @@ -0,0 +1,3 @@ +.dataSourceRadioHelperText { + text-indent: 24px; +} diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts index 4cafe9e1d195..a09f74635f1d 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts @@ -201,6 +201,7 @@ describe('getIndices', () => { getIndexTags, pattern: 'opensearch-dashboards', searchClient, + dataSourceId, }); expect(result.length).toBe(0); });