From a7ef1238ec8c3a0e17ae6234b4001750f4a79f76 Mon Sep 17 00:00:00 2001 From: Sumukh Swamy Date: Tue, 27 Aug 2024 15:48:59 -0700 Subject: [PATCH] [Backport 2.X] MDS support in Integrations for observability plugin (#2051) (#2096) * [FEATURE] MDS support in Integrations for observability plugin (#2051) * Support for MDS in integrations Signed-off-by: sumukhswamy * added changes for mds support in integrations Signed-off-by: sumukhswamy * fixed case for Local cluster Signed-off-by: sumukhswamy * addressed comments for undefined variables Signed-off-by: sumukhswamy * updated snapshots Signed-off-by: sumukhswamy * changed the refrences object in integrations instance Signed-off-by: sumukhswamy * addressed pr comments Signed-off-by: sumukhswamy * updated snapshots Signed-off-by: sumukhswamy * [Page Header] New page header for metrics (#2050) * Move the save button to the header control bar Signed-off-by: Ryan Liang * Update snapshots Signed-off-by: Ryan Liang * Fix the save button and correct its size + position Signed-off-by: Ryan Liang * Fix the date picker location Signed-off-by: Ryan Liang * Rename the navigation in coreRef and switch to use compressed date picker Signed-off-by: Ryan Liang * Fix the popover Signed-off-by: Ryan Liang * Rename the button Signed-off-by: Ryan Liang * Update to latest mockup Signed-off-by: Ryan Liang * Update snapshots Signed-off-by: Ryan Liang * Fix the ui issues Signed-off-by: Ryan Liang * Dummy metrics Signed-off-by: Ryan Liang * Remove dummy Signed-off-by: Ryan Liang * update snapshots Signed-off-by: Ryan Liang * minor changes to match mocks Signed-off-by: Shenoy Pratik --------- Signed-off-by: Ryan Liang Signed-off-by: Shenoy Pratik Co-authored-by: Shenoy Pratik Signed-off-by: sumukhswamy * Updated event analytics and integrations components Signed-off-by: sumukhswamy * updated tests and conditions Signed-off-by: sumukhswamy * updated snapshots Signed-off-by: sumukhswamy * updated snapshots Signed-off-by: sumukhswamy * updated snapshots Signed-off-by: sumukhswamy * updated the remote and local for snapshots Signed-off-by: sumukhswamy * updated snapshots Signed-off-by: sumukhswamy * updated snapshots again Signed-off-by: sumukhswamy * updated snapshots Signed-off-by: sumukhswamy * updated snapshots Signed-off-by: sumukhswamy * updated missing import Signed-off-by: sumukhswamy --------- Signed-off-by: sumukhswamy Signed-off-by: Ryan Liang Signed-off-by: Shenoy Pratik Signed-off-by: Sumukh Swamy Co-authored-by: Jialiang Liang Co-authored-by: Shenoy Pratik * linter fix Signed-off-by: sumukhswamy --------- Signed-off-by: sumukhswamy Signed-off-by: Ryan Liang Signed-off-by: Shenoy Pratik Signed-off-by: Sumukh Swamy Co-authored-by: Jialiang Liang Co-authored-by: Shenoy Pratik --- .../added_integration_table.test.tsx.snap | 1512 +++++++++++++++++ .../setup_integration.test.tsx.snap | 4 + .../added_integration_table.test.tsx | 14 +- .../components/__tests__/testing_constants.ts | 84 + .../components/added_integration.tsx | 37 +- .../added_integration_overview_page.tsx | 3 +- .../components/added_integration_table.tsx | 42 +- .../components/create_integration_helpers.ts | 44 +- .../integrations/components/integration.tsx | 125 +- .../components/integration_overview_panel.tsx | 4 +- .../components/integration_types.ts | 20 +- .../components/setup_integration.tsx | 78 +- .../components/setup_integration_inputs.tsx | 30 +- public/components/integrations/home.tsx | 12 + .../metrics/top_menu/metrics_export_panel.tsx | 2 +- .../components/metrics/top_menu/top_menu.tsx | 4 +- .../integrations/__test__/builder.test.ts | 2 +- .../integrations/integrations_builder.ts | 22 +- .../integrations/integrations_manager.ts | 8 +- server/adaptors/integrations/types.ts | 1 + server/routes/dsl.ts | 74 +- .../integrations/integrations_router.ts | 6 +- 22 files changed, 2038 insertions(+), 90 deletions(-) diff --git a/public/components/integrations/components/__tests__/__snapshots__/added_integration_table.test.tsx.snap b/public/components/integrations/components/__tests__/__snapshots__/added_integration_table.test.tsx.snap index be8c3a5e1b..e4286b273f 100644 --- a/public/components/integrations/components/__tests__/__snapshots__/added_integration_table.test.tsx.snap +++ b/public/components/integrations/components/__tests__/__snapshots__/added_integration_table.test.tsx.snap @@ -148,6 +148,7 @@ exports[`Added Integration Table View Test Renders added integration table view "name": "nginx", "templateName": "nginx", }, + "dataSourceMDSLabel": "Local cluster", "id": "ad7e6e30-0b99-11ee-b27c-c9863222e9bf", "name": "nginx", "templateName": "nginx", @@ -598,6 +599,7 @@ exports[`Added Integration Table View Test Renders added integration table view "name": "nginx", "templateName": "nginx", }, + "dataSourceMDSLabel": "Local cluster", "id": "ad7e6e30-0b99-11ee-b27c-c9863222e9bf", "name": "nginx", "templateName": "nginx", @@ -1503,3 +1505,1513 @@ exports[`Added Integration Table View Test Renders added integration table view `; + +exports[`Added Integration Table View Test Renders added integration table view using dummy data when MDS is disabled 1`] = ` + + + +
+ +
+ + +
+ +
+ + + +
+
+ + + + +
+ + + + + +
+
+
+
+
+
+
+
+
+ +
+ + +
+ + + Type + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + id="field_value_selection_0" + isOpen={false} + ownFocus={true} + panelClassName="euiFilterGroup__popoverPanel" + panelPaddingSize="none" + > +
+
+ + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + Integration Name + + + + + + + + + + + + Source + + + + + + + + + + + + Date Added + + + + + + + + + + + + Actions + + + + + +
+
+ Integration Name +
+
+ + + nginx + + +
+
+
+ Source +
+
+ + + nginx + + +
+
+
+ Date Added +
+
+ +
+ 2023-06-15T16:28:36.370Z +
+
+
+
+
+ Actions +
+
+ + + + + + + +
+
+
+
+ +
+ +
+ + + +
+ +
+ + + : + 10 + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="none" + > +
+
+ + + +
+
+
+
+
+ +
+ + + +
+
+
+
+
+
+ +
+ +
+ +
+ + + +`; diff --git a/public/components/integrations/components/__tests__/__snapshots__/setup_integration.test.tsx.snap b/public/components/integrations/components/__tests__/__snapshots__/setup_integration.test.tsx.snap index 0847a8db38..056538673d 100644 --- a/public/components/integrations/components/__tests__/__snapshots__/setup_integration.test.tsx.snap +++ b/public/components/integrations/components/__tests__/__snapshots__/setup_integration.test.tsx.snap @@ -42,6 +42,7 @@ exports[`Integration Setup Page Renders integration setup page as expected 1`] = "enabledWorkflows": Array [], } } + handleSelectedDataSourceChange={[Function]} integration={ Object { "assets": Array [], @@ -255,6 +256,7 @@ exports[`Integration Setup Page Renders integration setup page as expected 1`] = "enabledWorkflows": Array [], } } + handleSelectedDataSourceChange={[Function]} integration={ Object { "assets": Array [], @@ -935,6 +937,8 @@ exports[`Integration Setup Page Renders integration setup page as expected 1`] = "enabledWorkflows": Array [], } } + dataSourceMDSId="" + dataSourceMDSLabel="" integration={ Object { "assets": Array [], diff --git a/public/components/integrations/components/__tests__/added_integration_table.test.tsx b/public/components/integrations/components/__tests__/added_integration_table.test.tsx index 95e9c2bd11..726e881920 100644 --- a/public/components/integrations/components/__tests__/added_integration_table.test.tsx +++ b/public/components/integrations/components/__tests__/added_integration_table.test.tsx @@ -3,12 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { waitFor } from '@testing-library/react'; import { configure, mount } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; -import { waitFor } from '@testing-library/react'; -import { AddedIntegrationsTable } from '../added_integration_table'; -import { addedIntegrationData } from './testing_constants'; import React from 'react'; +import { AddedIntegrationsTable } from '../added_integration_table'; +import { addedIntegrationData, addedIntegrationDataWithoutMDS } from './testing_constants'; describe('Added Integration Table View Test', () => { configure({ adapter: new Adapter() }); @@ -20,4 +20,12 @@ describe('Added Integration Table View Test', () => { expect(wrapper).toMatchSnapshot(); }); }); + + it('Renders added integration table view using dummy data when MDS is disabled', async () => { + const wrapper = mount(); + + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); }); diff --git a/public/components/integrations/components/__tests__/testing_constants.ts b/public/components/integrations/components/__tests__/testing_constants.ts index ae6220e74a..465bc9ae90 100644 --- a/public/components/integrations/components/__tests__/testing_constants.ts +++ b/public/components/integrations/components/__tests__/testing_constants.ts @@ -175,6 +175,90 @@ export const addedIntegrationData: AddedIntegrationsTableProps = { loading: false, }; +export const addedIntegrationDataWithoutMDS: AddedIntegrationsTableProps = { + setData: () => {}, + http: httpClientMock, + data: { + hits: [ + { + name: 'nginx', + templateName: 'nginx', + dataSource: { sourceType: 'logs', dataset: 'nginx', namespace: 'prod' }, + creationDate: '2023-06-15T16:28:36.370Z', + status: 'active', + addedBy: 'admin', + assets: [ + { + assetType: 'index-pattern', + assetId: '3fc41705-8a23-49f4-926c-2819e0d7306d', + status: 'available', + isDefaultAsset: false, + description: 'ss4o_logs-nginx-prod', + }, + { + assetType: 'search', + assetId: 'a0415ddd-047d-4c02-8769-d14bfb70f525', + status: 'available', + isDefaultAsset: false, + description: '[NGINX Core Logs 1.0] Nginx Access Logs', + }, + { + assetType: 'visualization', + assetId: 'a17cd453-fb2f-4c24-81db-aedfc8682829', + status: 'available', + isDefaultAsset: false, + description: '[NGINX Core Logs 1.0] Response codes over time', + }, + { + assetType: 'search', + assetId: '3e47dfed-d9ff-4c1b-b425-04ffc8ed3fa9', + status: 'available', + isDefaultAsset: false, + description: '[NGINX Core Logs 1.0] Nginx Error Logs', + }, + { + assetType: 'visualization', + assetId: '641c2a03-eead-4900-94ee-e12d2fef8383', + status: 'available', + isDefaultAsset: false, + description: '[NGINX Core Logs 1.0] Errors over time', + }, + { + assetType: 'visualization', + assetId: 'ce61594d-8307-4358-9b7e-71101b3ed722', + status: 'available', + isDefaultAsset: false, + description: 'Data Volume', + }, + { + assetType: 'visualization', + assetId: '452bd6e3-3b50-407f-88f2-c35a29c56051', + status: 'available', + isDefaultAsset: false, + description: 'Top Paths', + }, + { + assetType: 'visualization', + assetId: '14a1ddab-08c1-4aba-ba3b-88bae36f7e50', + status: 'available', + isDefaultAsset: false, + description: 'Requests per Minute', + }, + { + assetType: 'dashboard', + assetId: '179bad58-c840-4c6c-9fd8-1667c14bd03a', + status: 'available', + isDefaultAsset: true, + description: '[NGINX Core Logs 1.0] Overview', + }, + ], + id: 'ad7e6e30-0b99-11ee-b27c-c9863222e9bf', + }, + ], + }, + loading: false, +}; + export const testIntegrationInstanceData = { data: { id: 'ad7e6e30-0b99-11ee-b27c-c9863222e9bf', diff --git a/public/components/integrations/components/added_integration.tsx b/public/components/integrations/components/added_integration.tsx index 523c54062e..9e69db507c 100644 --- a/public/components/integrations/components/added_integration.tsx +++ b/public/components/integrations/components/added_integration.tsx @@ -5,7 +5,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { - EuiSmallButtonIcon, EuiFlexGroup, EuiFlexItem, EuiHealth, @@ -18,18 +17,21 @@ import { EuiPageHeader, EuiPageHeaderSection, EuiPanel, + EuiSmallButtonIcon, EuiSpacer, EuiTableFieldDataColumnType, EuiText, } from '@elastic/eui'; -import React, { useEffect, useState } from 'react'; import _ from 'lodash'; -import { PanelTitle } from '../../trace_analytics/components/common/helper_functions'; +import React, { useEffect, useState } from 'react'; +import { DataSourceViewConfig } from '../../../../../../src/plugins/data_source_management/public'; import { ASSET_FILTER_OPTIONS } from '../../../../common/constants/integrations'; import { INTEGRATIONS_BASE } from '../../../../common/constants/shared'; +import { dataSourceFilterFn } from '../../../../common/utils/shared'; +import { useToast } from '../../../../public/components/common/toast'; import { DeleteModal } from '../../common/helpers/delete_modal'; +import { PanelTitle } from '../../trace_analytics/components/common/helper_functions'; import { AddedIntegrationProps } from './integration_types'; -import { useToast } from '../../../../public/components/common/toast'; export const IntegrationHealthBadge = ({ status }: { status?: string }) => { switch (status) { @@ -45,7 +47,14 @@ export const IntegrationHealthBadge = ({ status }: { status?: string }) => { }; export function AddedIntegration(props: AddedIntegrationProps) { - const { http, integrationInstanceId, chrome } = props; + const { + http, + integrationInstanceId, + chrome, + dataSourceEnabled, + dataSourceManagement, + setActionMenu, + } = props; const { setToast } = useToast(); @@ -74,6 +83,8 @@ export function AddedIntegration(props: AddedIntegrationProps) { const [isModalVisible, setIsModalVisible] = useState(false); const [modalLayout, setModalLayout] = useState(); + const DataSourceMenu = dataSourceManagement?.ui?.getDataSourceMenu(); + const activateDeleteModal = (integrationName?: string) => { setModalLayout( + {dataSourceEnabled && ( + + )} diff --git a/public/components/integrations/components/added_integration_overview_page.tsx b/public/components/integrations/components/added_integration_overview_page.tsx index 95d325146b..4890568017 100644 --- a/public/components/integrations/components/added_integration_overview_page.tsx +++ b/public/components/integrations/components/added_integration_overview_page.tsx @@ -18,7 +18,7 @@ export interface AddedIntegrationsTableProps { data: AddedIntegrationsList; setData: React.Dispatch>; http: HttpStart; - dataSourceEnabled: string; + dataSourceEnabled: boolean; } export interface AddedIntegrationsList { @@ -34,6 +34,7 @@ export interface AddedIntegrationType { assets: any[]; addedBy: string; id: string; + references?: []; } export function AddedIntegrationOverviewPage(props: AddedIntegrationOverviewPageProps) { diff --git a/public/components/integrations/components/added_integration_table.tsx b/public/components/integrations/components/added_integration_table.tsx index 6146434f33..ce23a9d6dd 100644 --- a/public/components/integrations/components/added_integration_table.tsx +++ b/public/components/integrations/components/added_integration_table.tsx @@ -93,7 +93,9 @@ export function AddedIntegrationsTable(props: AddedIntegrationsTableProps) { sortable: true, truncateText: true, render: (value, record) => ( - - - + + {truncate(record.dataSourceMDSLabel || 'Local cluster', { length: 100 })} + ), }); } @@ -132,8 +134,17 @@ export function AddedIntegrationsTable(props: AddedIntegrationsTableProps) { ); setIsModalVisible(true); }; - const integTemplateNames = [...new Set(props.data.hits.map((i) => i.templateName))].sort(); + let mdsLabels; + if (dataSourceEnabled) { + mdsLabels = [ + ...new Set( + props.data.hits.flatMap((hit) => + hit.references?.length > 0 ? hit.references.map((ref) => ref.name || 'Local cluster') : [] + ) + ), + ].sort(); + } const search = { box: { @@ -151,6 +162,21 @@ export function AddedIntegrationsTable(props: AddedIntegrationsTableProps) { view: name, })), }, + ...(dataSourceEnabled + ? [ + { + type: 'field_value_selection' as const, + field: 'dataSourceMDSLabel', + name: 'Data Source Name', + multiSelect: false, + options: mdsLabels?.map((name) => ({ + name, + value: name, + view: name, + })), + }, + ] + : []), ], }; @@ -159,7 +185,17 @@ export function AddedIntegrationsTable(props: AddedIntegrationsTableProps) { const templateName = integration.templateName; const creationDate = integration.creationDate; const name = integration.name; - return { id, templateName, creationDate, name, data: { templateName, name } }; + const dataSourceMDSLabel = integration.references + ? integration.references[0].name + : 'Local cluster'; + return { + id, + templateName, + creationDate, + name, + data: { templateName, name }, + dataSourceMDSLabel, + }; }); return ( diff --git a/public/components/integrations/components/create_integration_helpers.ts b/public/components/integrations/components/create_integration_helpers.ts index dcbc2da561..f6b8ed4c8a 100644 --- a/public/components/integrations/components/create_integration_helpers.ts +++ b/public/components/integrations/components/create_integration_helpers.ts @@ -2,10 +2,10 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ -import { Color, VALID_INDEX_NAME } from '../../../../common/constants/integrations'; import { HttpSetup } from '../../../../../../src/core/public'; +import { Color, VALID_INDEX_NAME } from '../../../../common/constants/integrations'; +import { CONSOLE_PROXY, DSL_BASE, INTEGRATIONS_BASE } from '../../../../common/constants/shared'; import { coreRefs } from '../../../framework/core_refs'; -import { CONSOLE_PROXY, INTEGRATIONS_BASE } from '../../../../common/constants/shared'; type ValidationResult = { ok: true } | { ok: false; errors: string[] }; @@ -18,6 +18,8 @@ interface AddIntegrationRequestParams { templateName: string; integration: IntegrationConfig; setToast: (title: string, color?: Color, text?: string | undefined) => void; + dataSourceMDSId?: string; + dataSourceMDSLabel?: string; name?: string; indexPattern?: string; workflows?: string[]; @@ -131,13 +133,13 @@ export const checkDataSourceName = ( export const fetchDataSourceMappings = async ( targetDataSource: string, - http: HttpSetup + http: HttpSetup, + dataSourceMDSId?: string ): Promise<{ [key: string]: { properties: Properties } } | null> => { return http - .post(CONSOLE_PROXY, { + .post(`${DSL_BASE}/integrations/mapping`, { query: { - path: `${targetDataSource}/_mapping`, - method: 'GET', + dataSourceMDSId, }, }) .then((response) => { @@ -204,7 +206,8 @@ export const doExistingDataSourceValidation = async ( const createComponentMapping = async ( componentName: string, - payload: ComponentMappingPayload + payload: ComponentMappingPayload, + dataSourceMDSId?: string ): Promise<{ [key: string]: { properties: Properties } } | null> => { const http = coreRefs.http!; const version = payload.template.mappings._meta.version; @@ -213,6 +216,7 @@ const createComponentMapping = async ( query: { path: `_component_template/ss4o_${componentName}-${version}-template`, method: 'POST', + dataSourceId: dataSourceMDSId, }, }); }; @@ -221,7 +225,8 @@ const createIndexMapping = async ( componentName: string, payload: ComponentMappingPayload, dataSourceName: string, - integration: IntegrationConfig + integration: IntegrationConfig, + dataSourceMDSId?: string ): Promise<{ [key: string]: { properties: Properties } } | null> => { const http = coreRefs.http!; const version = payload.template.mappings._meta.version; @@ -231,6 +236,7 @@ const createIndexMapping = async ( query: { path: `_index_template/ss4o_${componentName}-${integration.name}-${version}-sample`, method: 'POST', + dataSourceId: dataSourceMDSId, }, }); }; @@ -239,7 +245,8 @@ const createIndexPatternMappings = async ( targetDataSource: string, integrationTemplateId: string, integration: IntegrationConfig, - setToast: (title: string, color?: Color, text?: string | undefined) => void + setToast: (title: string, color?: Color, text?: string | undefined) => void, + dataSourceMDSId?: string ): Promise => { // TODO the nested methods still need the dataSource -> indexPattern rename applied, sub-methods // here still have old naming convention @@ -262,21 +269,21 @@ const createIndexPatternMappings = async ( if (key === integration.type) { return Promise.resolve(); } - return createComponentMapping(key, mapping as ComponentMappingPayload); + return createComponentMapping(key, mapping as ComponentMappingPayload, dataSourceMDSId); }) ); // In order to see our changes, we need to manually provoke a refresh - await http.post(CONSOLE_PROXY, { + await http.post(`${DSL_BASE}/integrations/refresh`, { query: { - path: '_refresh', - method: 'GET', + dataSourceMDSId, }, }); await createIndexMapping( integration.type, mappings[integration.type], targetDataSource, - integration + integration, + dataSourceMDSId ); } catch (err) { error = err.message; @@ -299,6 +306,8 @@ export async function addIntegrationRequest({ workflows, skipRedirect, dataSourceInfo, + dataSourceMDSId, + dataSourceMDSLabel, }: AddIntegrationRequestParams): Promise { const http = coreRefs.http!; if (addSample) { @@ -306,19 +315,24 @@ export async function addIntegrationRequest({ `ss4o_${integration.type}-${templateName}-*-sample`, templateName, integration, - setToast + setToast, + dataSourceMDSId ); name = `${templateName}-sample`; indexPattern = `ss4o_${integration.type}-${templateName}-sample-sample`; } const createReqBody: { + dataSourceMDSId?: string; + dataSourceMDSLabel?: string; name?: string; indexPattern?: string; workflows?: string[]; dataSource?: string; tableName?: string; } = { + dataSourceMDSId, + dataSourceMDSLabel, name, indexPattern, workflows, diff --git a/public/components/integrations/components/integration.tsx b/public/components/integrations/components/integration.tsx index d7b74dec0c..9ae5f3fc5d 100644 --- a/public/components/integrations/components/integration.tsx +++ b/public/components/integrations/components/integration.tsx @@ -5,7 +5,13 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { + EuiButton, EuiLoadingSpinner, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, EuiOverlayMask, EuiPage, EuiPageBody, @@ -13,21 +19,30 @@ import { EuiTab, EuiTabs, } from '@elastic/eui'; -import React, { useEffect, useState } from 'react'; -import { IntegrationOverview } from './integration_overview_panel'; -import { IntegrationDetails } from './integration_details_panel'; -import { IntegrationFields } from './integration_fields_panel'; -import { IntegrationAssets } from './integration_assets_panel'; -import { AvailableIntegrationProps } from './integration_types'; +import React, { ComponentType, useEffect, useState } from 'react'; +import { DataSourceSelectorProps } from '../../../../../../src/plugins/data_source_management/public/components/data_source_selector/data_source_selector'; import { INTEGRATIONS_BASE } from '../../../../common/constants/shared'; -import { IntegrationScreenshots } from './integration_screenshots_panel'; +import { dataSourceFilterFn } from '../../../../common/utils/shared'; import { useToast } from '../../../../public/components/common/toast'; import { coreRefs } from '../../../framework/core_refs'; import { addIntegrationRequest } from './create_integration_helpers'; +import { IntegrationAssets } from './integration_assets_panel'; +import { IntegrationDetails } from './integration_details_panel'; +import { IntegrationFields } from './integration_fields_panel'; +import { IntegrationOverview } from './integration_overview_panel'; +import { IntegrationScreenshots } from './integration_screenshots_panel'; +import { AvailableIntegrationProps } from './integration_types'; export function Integration(props: AvailableIntegrationProps) { const http = coreRefs.http!; - const { integrationTemplateId, chrome } = props; + const { + integrationTemplateId, + chrome, + notifications, + dataSourceEnabled, + dataSourceManagement, + savedObjectsMDSClient, + } = props; const { setToast } = useToast(); const [integration, setIntegration] = useState({} as IntegrationConfig); @@ -35,6 +50,11 @@ export function Integration(props: AvailableIntegrationProps) { const [integrationMapping, setMapping] = useState(null); const [integrationAssets, setAssets] = useState([]); const [loading, setLoading] = useState(false); + const [isModalVisible, setIsModalVisible] = useState(false); + const closeModal = () => setIsModalVisible(false); + const showModal = () => setIsModalVisible(true); + const [dataSourceMDSId, setDataSourceMDSId] = useState(''); + const [dataSourceMDSLabel, setDataSourceMDSLabel] = useState(''); useEffect(() => { chrome.setBreadcrumbs([ @@ -117,6 +137,21 @@ export function Integration(props: AvailableIntegrationProps) { setSelectedTabId(id); }; + let DataSourceSelector: + | ComponentType + | React.JSX.IntrinsicAttributes + | null; + if (dataSourceEnabled) { + DataSourceSelector = dataSourceManagement.ui.DataSourceSelector; + } + + const onSelectedDataSource = (e) => { + const dataConnectionId = e[0] ? e[0].id : undefined; + setDataSourceMDSId(dataConnectionId); + const dataConnectionLabel = e[0] ? e[0].label : undefined; + setDataSourceMDSLabel(dataConnectionLabel); + }; + const renderTabs = () => { return tabs.map((tab, index) => ( ; + + if (isModalVisible) { + modal = ( + + + +

Select Data Source

+
+
+ + + + + + + + Close + + { + setLoading(true); + await addIntegrationRequest({ + addSample: true, + templateName: integration.name, + integration, + setToast, + dataSourceMDSId, + dataSourceMDSLabel, + }); + setLoading(false); + closeModal(); + }} + isLoading={loading} + fill + > + Add + + +
+ ); + } + if (Object.keys(integration).length === 0) { return ( @@ -148,14 +234,20 @@ export function Integration(props: AvailableIntegrationProps) { window.location.hash = `#/available/${integration.name}/setup`; }} setUpSample={async () => { - setLoading(true); - await addIntegrationRequest({ - addSample: true, - templateName: integration.name, - integration, - setToast, - }); - setLoading(false); + if (dataSourceEnabled) { + showModal(); + } else { + setLoading(true); + await addIntegrationRequest({ + addSample: true, + templateName: integration.name, + integration, + setToast, + dataSourceMDSId, + dataSourceMDSLabel, + }); + setLoading(false); + } }} loading={loading} /> @@ -173,6 +265,7 @@ export function Integration(props: AvailableIntegrationProps) { : IntegrationFields({ integration, integrationMapping })} + {modal} ); } diff --git a/public/components/integrations/components/integration_overview_panel.tsx b/public/components/integrations/components/integration_overview_panel.tsx index 1eb301f193..47698e7e74 100644 --- a/public/components/integrations/components/integration_overview_panel.tsx +++ b/public/components/integrations/components/integration_overview_panel.tsx @@ -6,11 +6,11 @@ import { EuiButton, EuiFlexGroup, + EuiFlexItem, + EuiPageContentHeaderSection, EuiPageHeader, EuiPageHeaderSection, EuiSpacer, - EuiFlexItem, - EuiPageContentHeaderSection, EuiText, } from '@elastic/eui'; import React from 'react'; diff --git a/public/components/integrations/components/integration_types.ts b/public/components/integrations/components/integration_types.ts index a42bd87d2e..47c348edc4 100644 --- a/public/components/integrations/components/integration_types.ts +++ b/public/components/integrations/components/integration_types.ts @@ -3,7 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ChromeStart, HttpStart } from '../../../../../../src/core/public'; +import { + ChromeStart, + HttpStart, + MountPoint, + NotificationsStart, + SavedObjectsStart, +} from '../../../../../../src/core/public'; +import { DataSourceManagementPluginSetup } from '../../../../../../src/plugins/data_source_management/public'; export interface AvailableIntegrationOverviewPageProps { http: HttpStart; @@ -13,7 +20,7 @@ export interface AvailableIntegrationOverviewPageProps { export interface AddedIntegrationOverviewPageProps { http: HttpStart; chrome: ChromeStart; - dataSourceEnabled: string; + dataSourceEnabled: boolean; } export interface AvailableIntegrationProps { @@ -25,10 +32,19 @@ export interface AddedIntegrationProps { http: HttpStart; chrome: ChromeStart; integrationInstanceId: string; + notifications: NotificationsStart; + dataSourceEnabled: boolean; + dataSourceManagement: DataSourceManagementPluginSetup; + savedObjectsMDSClient: SavedObjectsStart; + setActionMenu: (menuMount: MountPoint | undefined) => void; } export interface AvailableIntegrationProps { http: HttpStart; chrome: ChromeStart; integrationTemplateId: string; + notifications: NotificationsStart; + dataSourceEnabled: boolean; + dataSourceManagement: DataSourceManagementPluginSetup; + savedObjectsMDSClient: SavedObjectsStart; } diff --git a/public/components/integrations/components/setup_integration.tsx b/public/components/integrations/components/setup_integration.tsx index 4090c2ded3..6d0e187ea5 100644 --- a/public/components/integrations/components/setup_integration.tsx +++ b/public/components/integrations/components/setup_integration.tsx @@ -5,8 +5,6 @@ import { EuiBottomBar, - EuiSmallButton, - EuiSmallButtonEmpty, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, @@ -17,12 +15,15 @@ import { EuiPageBody, EuiPageContent, EuiPageContentBody, + EuiSmallButton, + EuiSmallButtonEmpty, } from '@elastic/eui'; import React, { useEffect, useState } from 'react'; import { NotificationsStart, SavedObjectsStart } from '../../../../../../src/core/public'; import { DataSourceManagementPluginSetup } from '../../../../../../src/plugins/data_source_management/public'; import { Color } from '../../../../common/constants/integrations'; -import { CONSOLE_PROXY, INTEGRATIONS_BASE } from '../../../../common/constants/shared'; +import { INTEGRATIONS_BASE } from '../../../../common/constants/shared'; +import { SQLService } from '../../../../public/services/requests/sql'; import { coreRefs } from '../../../framework/core_refs'; import { addIntegrationRequest } from './create_integration_helpers'; import { SetupIntegrationFormInputs } from './setup_integration_inputs'; @@ -47,14 +48,18 @@ export interface IntegrationConfigProps { dataSourceEnabled: boolean; dataSourceManagement: DataSourceManagementPluginSetup; savedObjectsMDSClient: SavedObjectsStart; + handleSelectedDataSourceChange: (dataSourceMDSId?: string, dataSourceMDSLabel?: string) => void; } type SetupCallout = { show: true; title: string; color?: Color; text?: string } | { show: false }; +const sqlService = new SQLService(coreRefs.http!); + const runQuery = async ( query: string, datasource: string, - sessionId: string | null + sessionId: string | undefined, + dataSourceMDSId?: string ): Promise> => { // Used for polling const sleep = (ms: number) => { @@ -62,24 +67,22 @@ const runQuery = async ( }; try { - const http = coreRefs.http!; - const queryResponse: { queryId: string; sessionId: string } = await http.post(CONSOLE_PROXY, { - body: JSON.stringify({ query, datasource, lang: 'sql', sessionId }), - query: { - path: '_plugins/_async_query', - method: 'POST', + const queryResponse: { queryId: string; sessionId: string } = await sqlService.fetch( + { + query, + datasource, + lang: 'sql', + sessionId, }, - }); + dataSourceMDSId + ); + let poll: { status: string; error?: string } = { status: 'undefined' }; - const [queryId, newSessionId] = [queryResponse.queryId, queryResponse.sessionId]; + const { queryId, sessionId: newSessionId } = queryResponse; + while (!poll.error) { - poll = await http.post(CONSOLE_PROXY, { - body: '{}', - query: { - path: '_plugins/_async_query/' + queryId, - method: 'GET', - }, - }); + poll = await sqlService.fetchWithJobId({ queryId }, dataSourceMDSId); + if (poll.status.toLowerCase() === 'success') { return { ok: true, @@ -97,6 +100,7 @@ const runQuery = async ( } await sleep(3000); } + return { ok: false, error: new Error(poll.error) }; } catch (err) { console.error(err); @@ -133,16 +137,20 @@ const addIntegration = async ({ integration, setLoading, setCalloutLikeToast, + dataSourceMDSId, + dataSourceMDSLabel, setIsInstalling, }: { config: IntegrationSetupInputs; integration: IntegrationConfig; setLoading: (loading: boolean) => void; setCalloutLikeToast: (title: string, color?: Color, text?: string) => void; + dataSourceMDSId?: string; + dataSourceMDSLabel?: string; setIsInstalling?: (isInstalling: boolean, success?: boolean) => void; }) => { setLoading(true); - let sessionId: string | null = null; + let sessionId: string | undefined; if (config.connectionType === 'index') { let enabledWorkflows: string[] | undefined; @@ -158,6 +166,8 @@ const addIntegration = async ({ templateName: integration.name, integration, setToast: setCalloutLikeToast, + dataSourceMDSId, + dataSourceMDSLabel, name: config.displayName, indexPattern: config.connectionDataSource, skipRedirect: setIsInstalling ? true : false, @@ -175,7 +185,6 @@ const addIntegration = async ({ const assets: { data: ParsedIntegrationAsset[] } = await http.get( `${INTEGRATIONS_BASE}/repository/${integration.name}/assets` ); - for (const query of assets.data.filter( (a: ParsedIntegrationAsset): a is ParsedIntegrationAsset & { type: 'query' } => a.type === 'query' @@ -186,7 +195,12 @@ const addIntegration = async ({ } const queryStr = prepareQuery(query.query, config); - const result = await runQuery(queryStr, config.connectionDataSource, sessionId); + const result = await runQuery( + queryStr, + config.connectionDataSource, + sessionId, + dataSourceMDSId + ); if (!result.ok) { setLoading(false); setCalloutLikeToast('Failed to add integration', 'danger', result.error.message); @@ -200,6 +214,8 @@ const addIntegration = async ({ templateName: integration.name, integration, setToast: setCalloutLikeToast, + dataSourceMDSId, + dataSourceMDSLabel, name: config.displayName, indexPattern: `flint_${config.connectionDataSource}_default_${config.connectionTableName}__*`, workflows: config.enabledWorkflows, @@ -238,6 +254,8 @@ export function SetupBottomBar({ loading, setLoading, setSetupCallout, + dataSourceMDSId, + dataSourceMDSLabel, unsetIntegration, setIsInstalling, }: { @@ -246,6 +264,8 @@ export function SetupBottomBar({ loading: boolean; setLoading: (loading: boolean) => void; setSetupCallout: (setupCallout: SetupCallout) => void; + dataSourceMDSId?: string; + dataSourceMDSLabel?: string; unsetIntegration?: () => void; setIsInstalling?: (isInstalling: boolean, success?: boolean) => void; }) { @@ -299,6 +319,8 @@ export function SetupBottomBar({ setIsInstalling(newLoading); }, setCalloutLikeToast, + dataSourceMDSId, + dataSourceMDSLabel, setIsInstalling, }); } else { @@ -307,6 +329,8 @@ export function SetupBottomBar({ config, setLoading, setCalloutLikeToast, + dataSourceMDSId, + dataSourceMDSLabel, setIsInstalling, }); } @@ -377,6 +401,8 @@ export function SetupIntegrationForm({ const [setupCallout, setSetupCallout] = useState({ show: false } as SetupCallout); const [showLoading, setShowLoading] = useState(false); + const [dataSourceMDSId, setDataSourceMDSId] = useState(''); + const [dataSourceMDSLabel, setDataSourceMDSLabel] = useState(''); useEffect(() => { const getTemplate = async () => { @@ -391,6 +417,11 @@ export function SetupIntegrationForm({ setConfig(Object.assign({}, integConfig, updates)); const IntegrationInputFormComponent = SetupIntegrationFormInputs; + const handleSelectedDataSourceChange = (id?: string, label?: string) => { + setDataSourceMDSId(id); + setDataSourceMDSLabel(label); + }; + const content = ( <> {showLoading ? ( @@ -406,6 +437,7 @@ export function SetupIntegrationForm({ notifications={notifications} dataSourceEnabled={dataSourceEnabled} savedObjectsMDSClient={savedObjectsMDSClient} + handleSelectedDataSourceChange={handleSelectedDataSourceChange} /> )} @@ -418,6 +450,8 @@ export function SetupIntegrationForm({ loading={showLoading} setLoading={setShowLoading} setSetupCallout={setSetupCallout} + dataSourceMDSId={dataSourceMDSId} + dataSourceMDSLabel={dataSourceMDSLabel} unsetIntegration={unsetIntegration} setIsInstalling={setIsInstalling} /> diff --git a/public/components/integrations/components/setup_integration_inputs.tsx b/public/components/integrations/components/setup_integration_inputs.tsx index 152aa22eac..b78472bacd 100644 --- a/public/components/integrations/components/setup_integration_inputs.tsx +++ b/public/components/integrations/components/setup_integration_inputs.tsx @@ -8,9 +8,9 @@ import { EuiCheckableCard, EuiCompressedComboBox, EuiCompressedFieldText, - EuiForm, EuiCompressedFormRow, EuiCompressedSelect, + EuiForm, EuiSpacer, EuiText, } from '@elastic/eui'; @@ -18,6 +18,7 @@ import React, { useEffect, useState } from 'react'; import { NotificationsStart, SavedObjectsStart } from '../../../../../../src/core/public'; import { DataSourceManagementPluginSetup } from '../../../../../../src/plugins/data_source_management/public'; import { CONSOLE_PROXY, DATACONNECTIONS_BASE } from '../../../../common/constants/shared'; +import { dataSourceFilterFn } from '../../../../common/utils/shared'; import { coreRefs } from '../../../framework/core_refs'; import { IntegrationConfigProps, IntegrationSetupInputs } from './setup_integration'; @@ -59,7 +60,10 @@ const integrationConnectionSelectorItems = [ }, ]; -const suggestDataSources = async (type: string): Promise> => { +const suggestDataSources = async ( + type: string, + dataSourceMDSId?: string +): Promise> => { const http = coreRefs.http!; try { if (type === 'index') { @@ -68,6 +72,7 @@ const suggestDataSources = async (type: string): Promise; @@ -171,6 +178,7 @@ export function IntegrationConnectionInputs({ notifications, savedObjectsMDSClient, lockConnectionType, + handleSelectedDataSourceChange, }: { config: IntegrationSetupInputs; updateConfig: (updates: Partial) => void; @@ -179,9 +187,17 @@ export function IntegrationConnectionInputs({ dataSourceEnabled: boolean; dataSourceManagement: DataSourceManagementPluginSetup; savedObjectsMDSClient: SavedObjectsStart; + handleSelectedDataSourceChange: (dataSourceMDSId?: string, dataSourceMDSLabel?: string) => void; lockConnectionType?: boolean; }) { + const [dataSourceMDSId, setDataSourceMDSId] = useState(''); const connectionType = INTEGRATION_CONNECTION_DATA_SOURCE_TYPES.get(config.connectionType)!; + const onSelectedDataSource = (e) => { + const dataConnectionId = e[0] ? e[0].id : undefined; + setDataSourceMDSId(dataConnectionId); + const dataConnectionLabel = e[0] ? e[0].label : undefined; + handleSelectedDataSourceChange(dataConnectionId, dataConnectionLabel); + }; let DataSourceSelector; if (dataSourceEnabled) { @@ -194,14 +210,14 @@ export function IntegrationConnectionInputs({ useEffect(() => { const updateDataSources = async () => { - const data = await suggestDataSources(config.connectionType); + const data = await suggestDataSources(config.connectionType, dataSourceMDSId); setDataSourceSuggestions(data); setIsSuggestionsLoading(false); }; setIsSuggestionsLoading(true); updateDataSources(); - }, [config.connectionType]); + }, [config.connectionType, dataSourceMDSId]); return ( <> @@ -217,6 +233,8 @@ export function IntegrationConnectionInputs({ disabled={false} fullWidth={false} removePrepend={true} + onSelectedDataSource={onSelectedDataSource} + dataSourceFilter={dataSourceFilterFn} /> @@ -399,6 +417,7 @@ export function SetupIntegrationFormInputs(props: IntegrationConfigProps) { dataSourceManagement, notifications, savedObjectsMDSClient, + handleSelectedDataSourceChange, } = props; return ( @@ -436,6 +455,7 @@ export function SetupIntegrationFormInputs(props: IntegrationConfigProps) { notifications={notifications} dataSourceEnabled={dataSourceEnabled} savedObjectsMDSClient={savedObjectsMDSClient} + handleSelectedDataSourceChange={handleSelectedDataSourceChange} /> {config.connectionType === 's3' ? ( <> diff --git a/public/components/integrations/home.tsx b/public/components/integrations/home.tsx index e30215a639..0b99dfeba9 100644 --- a/public/components/integrations/home.tsx +++ b/public/components/integrations/home.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom'; import { ChromeBreadcrumb, + MountPoint, NotificationsStart, SavedObjectsStart, } from '../../../../../src/core/public'; @@ -26,6 +27,7 @@ interface HomeProps extends RouteComponentProps, AppAnalyticsCoreDeps { dataSourceEnabled: boolean; dataSourceManagement: DataSourceManagementPluginSetup; savedObjectsMDSClient: SavedObjectsStart; + setActionMenu: (menuMount: MountPoint | undefined) => void; } export const Home = (props: HomeProps) => { @@ -36,6 +38,7 @@ export const Home = (props: HomeProps) => { dataSourceManagement, savedObjectsMDSClient, notifications, + setActionMenu, } = props; const commonProps = { @@ -69,6 +72,11 @@ export const Home = (props: HomeProps) => { )} /> @@ -79,6 +87,10 @@ export const Home = (props: HomeProps) => { )} /> diff --git a/public/components/metrics/top_menu/metrics_export_panel.tsx b/public/components/metrics/top_menu/metrics_export_panel.tsx index ce8cb3e51c..7ac52a7a1f 100644 --- a/public/components/metrics/top_menu/metrics_export_panel.tsx +++ b/public/components/metrics/top_menu/metrics_export_panel.tsx @@ -6,8 +6,8 @@ import { EuiCompressedComboBox, EuiCompressedFieldText, - EuiFlexGroup, EuiCompressedFormRow, + EuiFlexGroup, EuiFlexItem, EuiForm, EuiHorizontalRule, diff --git a/public/components/metrics/top_menu/top_menu.tsx b/public/components/metrics/top_menu/top_menu.tsx index 92c7adf89d..c1da763684 100644 --- a/public/components/metrics/top_menu/top_menu.tsx +++ b/public/components/metrics/top_menu/top_menu.tsx @@ -5,10 +5,10 @@ import { EuiCompressedFieldText, - EuiFlexGroup, - EuiFlexItem, EuiCompressedSelect, EuiCompressedSuperDatePicker, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; diff --git a/server/adaptors/integrations/__test__/builder.test.ts b/server/adaptors/integrations/__test__/builder.test.ts index ca596e213b..4ebec0ad35 100644 --- a/server/adaptors/integrations/__test__/builder.test.ts +++ b/server/adaptors/integrations/__test__/builder.test.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { TEST_INTEGRATION_CONFIG } from '../../../../test/constants'; import { SavedObjectsClientContract } from '../../../../../../src/core/server'; +import { TEST_INTEGRATION_CONFIG } from '../../../../test/constants'; import { IntegrationInstanceBuilder } from '../integrations_builder'; import { IntegrationReader } from '../repository/integration_reader'; import * as mockUtils from '../repository/utils'; diff --git a/server/adaptors/integrations/integrations_builder.ts b/server/adaptors/integrations/integrations_builder.ts index 41b0dc53f9..033acf5805 100644 --- a/server/adaptors/integrations/integrations_builder.ts +++ b/server/adaptors/integrations/integrations_builder.ts @@ -4,14 +4,16 @@ */ import { v4 as uuidv4 } from 'uuid'; +import { SavedObjectsBulkCreateObject } from '../../../../../src/core/public'; import { SavedObjectsClientContract } from '../../../../../src/core/server'; import { IntegrationReader } from './repository/integration_reader'; -import { SavedObjectsBulkCreateObject } from '../../../../../src/core/public'; import { deepCheck } from './repository/utils'; interface BuilderOptions { name: string; indexPattern: string; + dataSourceMDSId?: string; + dataSourceMDSLabel?: string; workflows?: string[]; dataSource?: string; tableName?: string; @@ -186,15 +188,23 @@ export class IntegrationInstanceBuilder { new Error('Attempted to create instance with invalid template', config.error) ); } - return Promise.resolve({ + const instance: IntegrationInstance = { name: options.name, templateName: config.value.name, - // Before data sources existed we called the index pattern a data source. Now we need the old - // name for BWC but still use the new data sources in building, so we map the variable only - // for returned output here dataSource: options.indexPattern, creationDate: new Date().toISOString(), assets: refs, - }); + }; + if (options.dataSourceMDSId) { + instance.references = [ + { + id: options.dataSourceMDSId, + name: options.dataSourceMDSLabel, + type: 'data-source', + }, + ]; + } + + return Promise.resolve(instance); } } diff --git a/server/adaptors/integrations/integrations_manager.ts b/server/adaptors/integrations/integrations_manager.ts index e24ccfae1e..5ac26edb88 100644 --- a/server/adaptors/integrations/integrations_manager.ts +++ b/server/adaptors/integrations/integrations_manager.ts @@ -4,12 +4,12 @@ */ import path from 'path'; -import { addRequestToMetric } from '../../common/metrics/metrics_helper'; import { SavedObject, SavedObjectsClientContract } from '../../../../../src/core/server/types'; +import { addRequestToMetric } from '../../common/metrics/metrics_helper'; import { IntegrationInstanceBuilder } from './integrations_builder'; -import { TemplateManager } from './repository/repository'; import { FileSystemDataAdaptor } from './repository/fs_data_adaptor'; import { IndexDataAdaptor } from './repository/index_data_adaptor'; +import { TemplateManager } from './repository/repository'; export class IntegrationsManager { client: SavedObjectsClientContract; @@ -157,6 +157,8 @@ export class IntegrationsManager { templateName: string, name: string, indexPattern: string, + dataSourceMDSId?: string, + dataSourceMDSLabel?: string, workflows?: string[], dataSource?: string, tableName?: string @@ -173,6 +175,8 @@ export class IntegrationsManager { const result = await this.instanceBuilder.build(template, { name, indexPattern, + dataSourceMDSId, + dataSourceMDSLabel, workflows, dataSource, tableName, diff --git a/server/adaptors/integrations/types.ts b/server/adaptors/integrations/types.ts index 95a7895041..0f229526f9 100644 --- a/server/adaptors/integrations/types.ts +++ b/server/adaptors/integrations/types.ts @@ -113,6 +113,7 @@ interface IntegrationInstance { dataSource: string; creationDate: string; assets: AssetReference[]; + references?: []; } interface IntegrationInstanceResult extends IntegrationInstance { diff --git a/server/routes/dsl.ts b/server/routes/dsl.ts index 0560ae049f..43bec95f1f 100644 --- a/server/routes/dsl.ts +++ b/server/routes/dsl.ts @@ -3,17 +3,17 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { schema } from '@osd/config-schema'; import { RequestParams } from '@elastic/elasticsearch'; +import { schema } from '@osd/config-schema'; import { IRouter } from '../../../../src/core/server'; -import { DSLFacet } from '../services/facets/dsl_facet'; import { DSL_BASE, - DSL_SEARCH, DSL_CAT, DSL_MAPPING, + DSL_SEARCH, DSL_SETTINGS, } from '../../common/constants/shared'; +import { DSLFacet } from '../services/facets/dsl_facet'; export function registerDslRoute( { router }: { router: IRouter; facet: DSLFacet }, @@ -236,4 +236,72 @@ export function registerDslRoute( } } ); + + router.post( + { + path: `${DSL_BASE}/integrations/refresh`, + validate: { + body: schema.any(), + query: schema.object({ + dataSourceMDSId: schema.maybe(schema.string({ defaultValue: '' })), + }), + }, + }, + async (context, request, response) => { + const dataSourceMDSId = request.query.dataSourceMDSId; + try { + let resp; + if (dataSourceEnabled && dataSourceMDSId) { + const client = await context.dataSource.opensearch.legacy.getClient(dataSourceMDSId); + resp = await client.callAPI('indices.refresh'); + } else { + resp = await context.core.opensearch.legacy.client.callAsCurrentUser('indices.refresh'); + } + return response.ok({ + body: resp, + }); + } catch (error) { + if (error.statusCode !== 404) console.error(error); + return response.custom({ + statusCode: error.statusCode || 500, + body: error.message, + }); + } + } + ); + + router.post( + { + path: `${DSL_BASE}/integrations/mapping`, + validate: { + body: schema.any(), + query: schema.object({ + dataSourceMDSId: schema.maybe(schema.string({ defaultValue: '' })), + }), + }, + }, + async (context, request, response) => { + const dataSourceMDSId = request.query.dataSourceMDSId; + try { + let resp; + if (dataSourceEnabled && dataSourceMDSId) { + const client = await context.dataSource.opensearch.legacy.getClient(dataSourceMDSId); + resp = await client.callAPI('indices.getMapping'); + } else { + resp = await context.core.opensearch.legacy.client.callAsCurrentUser( + 'indices.getMapping' + ); + } + return response.ok({ + body: resp, + }); + } catch (error) { + if (error.statusCode !== 404) console.error(error); + return response.custom({ + statusCode: error.statusCode || 500, + body: error.message, + }); + } + } + ); } diff --git a/server/routes/integrations/integrations_router.ts b/server/routes/integrations/integrations_router.ts index 68eed60d91..d48077af35 100644 --- a/server/routes/integrations/integrations_router.ts +++ b/server/routes/integrations/integrations_router.ts @@ -7,11 +7,11 @@ import { schema } from '@osd/config-schema'; import * as mime from 'mime'; import sanitize from 'sanitize-filename'; import { IRouter } from '../../../../../src/core/server'; -import { INTEGRATIONS_BASE } from '../../../common/constants/shared'; import { OpenSearchDashboardsResponse, OpenSearchDashboardsResponseFactory, } from '../../../../../src/core/server/http/router'; +import { INTEGRATIONS_BASE } from '../../../common/constants/shared'; import { IntegrationsManager } from '../../adaptors/integrations/integrations_manager'; /** @@ -71,6 +71,8 @@ export function registerIntegrationsRoute(router: IRouter) { templateName: schema.string(), }), body: schema.object({ + dataSourceMDSId: schema.maybe(schema.string({ defaultValue: '' })), + dataSourceMDSLabel: schema.maybe(schema.string({ defaultValue: '' })), name: schema.string(), indexPattern: schema.string(), workflows: schema.maybe(schema.arrayOf(schema.string())), @@ -86,6 +88,8 @@ export function registerIntegrationsRoute(router: IRouter) { request.params.templateName, request.body.name, request.body.indexPattern, + request.body.dataSourceMDSId, + request.body.dataSourceMDSLabel, request.body.workflows, request.body.dataSource, request.body.tableName