diff --git a/common/constants/shared.ts b/common/constants/shared.ts index c6d3c781b0..0775b9aef0 100644 --- a/common/constants/shared.ts +++ b/common/constants/shared.ts @@ -93,6 +93,7 @@ export const PPL_INDEX_INSERT_POINT_REGEX = /(search source|source|index)\s*=\s* export const PPL_INDEX_REGEX = /(search source|source|index)\s*=\s*([^|\s]+)/i; export const PPL_WHERE_CLAUSE_REGEX = /\s*where\s+/i; export const PPL_NEWLINE_REGEX = /[\n\r]+/g; +export const PPL_DESCRIBE_INDEX_REGEX = /(describe)\s+([^|\s]+)/i; // Observability plugin URI const BASE_OBSERVABILITY_URI = '/_plugins/_observability'; diff --git a/public/components/common/query_utils/index.ts b/public/components/common/query_utils/index.ts index 9e441de0c8..826850f331 100644 --- a/public/components/common/query_utils/index.ts +++ b/public/components/common/query_utils/index.ts @@ -21,6 +21,7 @@ import { OTEL_DATE_FORMAT, OTEL_METRIC_SUBTYPE, PROMQL_METRIC_SUBTYPE, + PPL_DESCRIBE_INDEX_REGEX, } from '../../../../common/constants/shared'; import { IExplorerFields, IQuery } from '../../../../common/types/explorer'; import { SPAN_RESOLUTION_REGEX } from '../../../../common/constants/metrics'; @@ -219,6 +220,14 @@ export const getIndexPatternFromRawQuery = (query: string): string => { return getPromQLIndex(query) || getPPLIndex(query); }; +export const getDescribeQueryIndexFromRawQuery = (query: string): string | undefined => { + const matches = query.match(PPL_DESCRIBE_INDEX_REGEX); + if (matches) { + return matches[2]; + } + return undefined; +}; + function extractSpanAndResolution(query: string) { if (!query) return; diff --git a/public/components/event_analytics/explorer/explorer.tsx b/public/components/event_analytics/explorer/explorer.tsx index a3286c1c57..c20fea643a 100644 --- a/public/components/event_analytics/explorer/explorer.tsx +++ b/public/components/event_analytics/explorer/explorer.tsx @@ -44,6 +44,7 @@ import { DATE_PICKER_FORMAT, DEFAULT_AVAILABILITY_QUERY, EVENT_ANALYTICS_DOCUMENTATION_URL, + FINAL_QUERY, PATTERNS_EXTRACTOR_REGEX, PATTERNS_REGEX, RAW_QUERY, @@ -63,6 +64,7 @@ import { QUERY_ASSIST_API } from '../../../../common/constants/query_assist'; import { LIVE_END_TIME, LIVE_OPTIONS, + PPL_DESCRIBE_INDEX_REGEX, PPL_METRIC_SUBTYPE, PPL_NEWLINE_REGEX, } from '../../../../common/constants/shared'; @@ -540,12 +542,17 @@ export const Explorer = ({ return 0; }, [countDistribution?.data]); + const showTimeBasedComponents = + (isDefaultDataSourceType || appLogEvents) && + query[SELECTED_TIMESTAMP] !== '' && + !query[FINAL_QUERY].match(PPL_DESCRIBE_INDEX_REGEX); + const mainContent = useMemo(() => { return (
{explorerData && !isEmpty(explorerData.jsonData) ? ( - {(isDefaultDataSourceType || appLogEvents) && query[SELECTED_TIMESTAMP] !== '' && ( + {showTimeBasedComponents && ( <> @@ -594,7 +601,7 @@ export const Explorer = ({ )} - {(isDefaultDataSourceType || appLogEvents) && query[SELECTED_TIMESTAMP] !== '' && ( + {showTimeBasedComponents && ( @@ -643,11 +650,14 @@ export const Explorer = ({ rows={explorerData.jsonData} rowsAll={explorerData.jsonDataAll} explorerFields={explorerFields} - timeStampField={queryRef.current![SELECTED_TIMESTAMP]} + timeStampField={ + !query[FINAL_QUERY].match(PPL_DESCRIBE_INDEX_REGEX) + ? queryRef.current![SELECTED_TIMESTAMP] + : '' + } rawQuery={appBasedRef.current || queryRef.current![RAW_QUERY]} totalHits={ - (isDefaultDataSourceType || appLogEvents) && - query[SELECTED_TIMESTAMP] !== '' + showTimeBasedComponents ? _.sum(countDistribution.data?.['count()']) || explorerData.datarows.length : explorerData.datarows.length @@ -656,7 +666,7 @@ export const Explorer = ({ startTime={startTime} endTime={endTime} isDefaultDataSource={ - explorerSearchMeta.datasources[0].type === DEFAULT_DATA_SOURCE_TYPE + explorerSearchMeta?.datasources?.[0]?.type === DEFAULT_DATA_SOURCE_TYPE } /> )} 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 84af29f655..76ed541c9d 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 @@ -95,91 +95,116 @@ exports[`Integration Setup Page Renders integration setup page as expected 1`] = className="euiSpacer euiSpacer--l" /> - -
- - - -
-
- + Display Name + + +
+
- -
- - - - + + + + +
-
- - + + +
- - + +
- -
- - - -
-
- + Connection Type + + +
+
- -
- - + + - - - - - -
- + + + + + + + +
+ +
-
- - - -
+ + - Select the type of connection to use for queries. -
-
+
+ Select the type of connection to use for queries. +
+ + - - - -
+
- - - -
-
- + Index + + +
+
-
- +
- -
- - - - -
+ + + -
-
-
-
- + +
+
+
+
+ -
- - + + + + + + + + - - - - - - - - -
-
+ +
+ +
-
- - -
- - -
+ +
+ + - Select an index to pull the data from. -
- +
+ Select an index to pull the data from. +
+ + - -
+ + @@ -1070,2993 +1121,3 @@ exports[`Integration Setup Page Renders integration setup page as expected 1`] = `; - -exports[`Integration Setup Page Renders the S3 connector form as expected 1`] = ` - - -
- -

- Set Up Integration -

-
- -
- - -
- - -
-

- Integration Details -

-
-
- -
- - -
-
- - - -
-
- - -
-
- - - - -
-
-
-
-
-
-
- -
- - -
-

- Integration Connection -

-
-
- -
- - -
-
- - - -
-
- - -
-
- - - - -
- - - - - -
-
-
-
-
-
- -
- Select the type of connection to use for queries. -
-
-
-
-
- -
-
- - - -
-
- -
- - -
-
-
- - - ss4o_logs-nginx-test - - - -
- -
-
- -
- -
- - - - - - - - - - - -
-
-
-
- - -
- - -
- Select a data source to pull the data from. -
-
-
-
- - -
-
- - - -
-
- - -
-
- - - - -
-
-
-
- -
- Must be at least 1 character. -
-
- -
- Select a table name to associate with your data. -
-
-
-
-
- -
-
- - - -
-
- - -
-
- - - - -
-
-
-
-
-
-
- -
-
- - - -
-
- - -
-
- - - - -
-
-
-
- -
- The Checkpoint location must be a unique directory and not the same as the Bucket location. It will be used for caching intermediary results. -
-
-
-
-
- -
- - -
-

- Integration Resources -

-
-
- -
-
- -
-

- This integration offers valuable resources compatible with your data source. These can include dashboards, visualizations, indexes, and queries. Select at least one of the following options. -

-
-
-
-
-
- -
- - -
-
- - - <_EuiSplitPanelOuter - className="euiCheckableCard euiCheckableCard-isChecked" - direction="row" - hasBorder={true} - responsive={false} - > - -
- <_EuiSplitPanelInner - color="primary" - grow={false} - onClick={[Function]} - > - -
- -
- -
-
- -
- - - <_EuiSplitPanelInner> - -
- -
- This is a test workflow. -
-
-
- -
-
- - - -
-
- - -
- - -
- -
- - -`; - -exports[`Integration Setup Page Renders the S3 connector form without workflows 1`] = ` - - -
- -

- Set Up Integration -

-
- -
- - -
- - -
-

- Integration Details -

-
-
- -
- - -
-
- - - -
-
- - -
-
- - - - -
-
-
-
-
-
-
- -
- - -
-

- Integration Connection -

-
-
- -
- - -
-
- - - -
-
- - -
-
- - - - -
- - - - - -
-
-
-
-
-
- -
- Select the type of connection to use for queries. -
-
-
-
-
- -
-
- - - -
-
- -
- - -
-
-
- - - ss4o_logs-nginx-test - - - -
- -
-
- -
- -
- - - - - - - - - - - -
-
-
-
- - -
- - -
- Select a data source to pull the data from. -
-
-
-
- - -
-
- - - -
-
- - -
-
- - - - -
-
-
-
- -
- Must be at least 1 character. -
-
- -
- Select a table name to associate with your data. -
-
-
-
-
- -
-
- - - -
-
- - -
-
- - - - -
-
-
-
-
-
-
- -
-
- - - -
-
- - -
-
- - - - -
-
-
-
- -
- The Checkpoint location must be a unique directory and not the same as the Bucket location. It will be used for caching intermediary results. -
-
-
-
-
- -
- - -
- -
- - -`; - -exports[`Integration Setup Page Renders the index form as expected 1`] = ` - - -
- -

- Set Up Integration -

-
- -
- - -
- - -
-

- Integration Details -

-
-
- -
- - -
-
- - - -
-
- - -
-
- - - - -
-
-
-
-
-
-
- -
- - -
-

- Integration Connection -

-
-
- -
- - -
-
- - - -
-
- - -
-
- - - - -
- - - - - -
-
-
-
-
-
- -
- Select the type of connection to use for queries. -
-
-
-
-
- -
-
- - - -
-
- -
- - -
-
-
- - - ss4o_logs-nginx-test - - - -
- -
-
- -
- -
- - - - - - - - - - - -
-
-
-
- - -
- - -
- Select an index to pull the data from. -
-
-
-
- -
- - -`; diff --git a/public/components/integrations/components/__tests__/__snapshots__/setup_integration_inputs.test.tsx.snap b/public/components/integrations/components/__tests__/__snapshots__/setup_integration_inputs.test.tsx.snap new file mode 100644 index 0000000000..08bdef5367 --- /dev/null +++ b/public/components/integrations/components/__tests__/__snapshots__/setup_integration_inputs.test.tsx.snap @@ -0,0 +1,2374 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Integration Setup Inputs Renders the S3 connector form as expected 1`] = ` + + +

+ Set Up Integration +

+
+ + + +

+ Integration Details +

+
+ + + + +

+ Integration Connection +

+
+ + + + +

+ Query Fields +

+
+ + +

+ To set up the integration, we need to know some information about how to process your data. +

+
+
+ + + + +

+ Integration Resources +

+
+ + +

+ This integration offers different kinds of resources compatible with your data source. These can include dashboards, visualizations, indexes, and queries. Select at least one of the following options. +

+
+
+ + + + +
+`; + +exports[`Integration Setup Inputs Renders the S3 connector form without workflows 1`] = ` + + +

+ Set Up Integration +

+
+ + + +

+ Integration Details +

+
+ + + + +

+ Integration Connection +

+
+ + + + +

+ Query Fields +

+
+ + +

+ To set up the integration, we need to know some information about how to process your data. +

+
+
+ + + + +

+ Integration Resources +

+
+ + +

+ This integration offers different kinds of resources compatible with your data source. These can include dashboards, visualizations, indexes, and queries. Select at least one of the following options. +

+
+
+ + + + +
+`; + +exports[`Integration Setup Inputs Renders the connection inputs 1`] = ` + + +
+
+ + + +
+
+ + +
+
+ + + + +
+ + + + + +
+
+
+
+
+
+ +
+ Select the type of connection to use for queries. +
+
+
+
+
+ +
+
+ + + +
+
+ +
+ + +
+
+
+ + + ss4o_logs-nginx-test + + + +
+ +
+
+ +
+ +
+ + + + + + + + + + + +
+
+
+
+ + +
+ + +
+ Select a data source to pull the data from. +
+
+
+
+ + +`; + +exports[`Integration Setup Inputs Renders the connection inputs with a locked connection type 1`] = ` + + +
+
+ + + +
+
+ + +
+
+ + + + +
+ + + + + +
+
+
+
+
+
+ +
+ Select the type of connection to use for queries. +
+
+
+
+
+ +
+
+ + + +
+
+ +
+ + +
+
+
+ + + ss4o_logs-nginx-test + + + +
+ +
+
+ +
+ +
+ + + + + + +
+
+
+
+ + +
+ + +
+ Select a data source to pull the data from. +
+
+
+
+ + +`; + +exports[`Integration Setup Inputs Renders the details inputs 1`] = ` + + +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+
+
+
+
+`; + +exports[`Integration Setup Inputs Renders the index form as expected 1`] = ` + + +

+ Set Up Integration +

+
+ + + +

+ Integration Details +

+
+ + + + +

+ Integration Connection +

+
+ + +
+`; + +exports[`Integration Setup Inputs Renders the query inputs 1`] = ` + + +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+ +
+ Must be at least 1 character. +
+
+ +
+ Select a table name to associate with your data. +
+
+
+
+
+ +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+
+
+
+ +
+
+ + + +
+
+ + +
+
+ + + + +
+
+
+
+ +
+ The Checkpoint location must be a unique directory and not the same as the Data location. It will be used for caching intermediary results. +
+
+
+
+
+
+`; + +exports[`Integration Setup Inputs Renders the workflows inputs 1`] = ` + + +
+
+ + + <_EuiSplitPanelOuter + className="euiCheckableCard euiCheckableCard-isChecked" + direction="row" + hasBorder={true} + responsive={false} + > + +
+ <_EuiSplitPanelInner + color="primary" + grow={false} + onClick={[Function]} + > + +
+ +
+ +
+
+ +
+ + + <_EuiSplitPanelInner> + +
+ +
+ This is a test workflow. +
+
+
+ +
+
+ + + +
+
+ + +`; diff --git a/public/components/integrations/components/__tests__/setup_integration.test.tsx b/public/components/integrations/components/__tests__/setup_integration.test.tsx index 1fb32133ec..e975700a0f 100644 --- a/public/components/integrations/components/__tests__/setup_integration.test.tsx +++ b/public/components/integrations/components/__tests__/setup_integration.test.tsx @@ -7,11 +7,8 @@ import { configure, mount } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import React from 'react'; import { waitFor } from '@testing-library/react'; -import { SetupIntegrationPage, SetupIntegrationFormInputs } from '../setup_integration'; -import { - TEST_INTEGRATION_CONFIG, - TEST_INTEGRATION_SETUP_INPUTS, -} from '../../../../../test/constants'; +import { SetupIntegrationPage } from '../setup_integration'; +import { TEST_INTEGRATION_CONFIG } from '../../../../../test/constants'; describe('Integration Setup Page', () => { configure({ adapter: new Adapter() }); @@ -23,49 +20,4 @@ describe('Integration Setup Page', () => { expect(wrapper).toMatchSnapshot(); }); }); - - it('Renders the index form as expected', async () => { - const wrapper = mount( - {}} - integration={TEST_INTEGRATION_CONFIG} - setupCallout={{ show: false }} - /> - ); - - await waitFor(() => { - expect(wrapper).toMatchSnapshot(); - }); - }); - - it('Renders the S3 connector form as expected', async () => { - const wrapper = mount( - {}} - integration={TEST_INTEGRATION_CONFIG} - setupCallout={{ show: false }} - /> - ); - - await waitFor(() => { - expect(wrapper).toMatchSnapshot(); - }); - }); - - it('Renders the S3 connector form without workflows', async () => { - const wrapper = mount( - {}} - integration={{ ...TEST_INTEGRATION_CONFIG, workflows: undefined }} - setupCallout={{ show: false }} - /> - ); - - await waitFor(() => { - expect(wrapper).toMatchSnapshot(); - }); - }); }); diff --git a/public/components/integrations/components/__tests__/setup_integration_inputs.test.tsx b/public/components/integrations/components/__tests__/setup_integration_inputs.test.tsx new file mode 100644 index 0000000000..8f0ef853ae --- /dev/null +++ b/public/components/integrations/components/__tests__/setup_integration_inputs.test.tsx @@ -0,0 +1,136 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { configure, mount, shallow } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import React from 'react'; +import { waitFor } from '@testing-library/react'; +import { + IntegrationConnectionInputs, + IntegrationDetailsInputs, + IntegrationQueryInputs, + IntegrationWorkflowsInputs, + SetupIntegrationFormInputs, +} from '../setup_integration_inputs'; +import { + TEST_INTEGRATION_CONFIG, + TEST_INTEGRATION_SETUP_INPUTS, +} from '../../../../../test/constants'; + +describe('Integration Setup Inputs', () => { + configure({ adapter: new Adapter() }); + + it('Renders the index form as expected', async () => { + const wrapper = shallow( + {}} + integration={TEST_INTEGRATION_CONFIG} + setupCallout={{ show: false }} + /> + ); + + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); + + it('Renders the S3 connector form as expected', async () => { + const wrapper = shallow( + {}} + integration={TEST_INTEGRATION_CONFIG} + setupCallout={{ show: false }} + /> + ); + + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); + + it('Renders the S3 connector form without workflows', async () => { + const wrapper = shallow( + {}} + integration={TEST_INTEGRATION_CONFIG} + setupCallout={{ show: false }} + /> + ); + + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); + + it('Renders the details inputs', async () => { + const wrapper = mount( + {}} + integration={TEST_INTEGRATION_CONFIG} + /> + ); + + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); + + it('Renders the connection inputs', async () => { + const wrapper = mount( + {}} + integration={TEST_INTEGRATION_CONFIG} + /> + ); + + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); + + it('Renders the connection inputs with a locked connection type', async () => { + const wrapper = mount( + {}} + integration={TEST_INTEGRATION_CONFIG} + lockConnectionType={true} + /> + ); + + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); + + it('Renders the query inputs', async () => { + const wrapper = mount( + {}} + integration={TEST_INTEGRATION_CONFIG} + /> + ); + + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); + + it('Renders the workflows inputs', async () => { + const wrapper = mount( + {}} integration={TEST_INTEGRATION_CONFIG} /> + ); + + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/integrations/components/create_integration_helpers.ts b/public/components/integrations/components/create_integration_helpers.ts index 1ef8c71ead..dcbc2da561 100644 --- a/public/components/integrations/components/create_integration_helpers.ts +++ b/public/components/integrations/components/create_integration_helpers.ts @@ -13,6 +13,18 @@ interface Properties { [key: string]: Properties | object; } +interface AddIntegrationRequestParams { + addSample: boolean; + templateName: string; + integration: IntegrationConfig; + setToast: (title: string, color?: Color, text?: string | undefined) => void; + name?: string; + indexPattern?: string; + workflows?: string[]; + skipRedirect?: boolean; + dataSourceInfo?: { dataSource: string; tableName: string }; +} + interface ComponentMappingPayload { template: { mappings: { _meta: { version: string } } }; composed_of: string[]; @@ -277,28 +289,27 @@ const createIndexPatternMappings = async ( } }; -export async function addIntegrationRequest( - addSample: boolean, - templateName: string, - integrationTemplateId: string, - integration: IntegrationConfig, - setToast: (title: string, color?: Color, text?: string | undefined) => void, - name?: string, - indexPattern?: string, - workflows?: string[], - skipRedirect?: boolean, - dataSourceInfo?: { dataSource: string; tableName: string } -): Promise { +export async function addIntegrationRequest({ + addSample, + templateName, + integration, + setToast, + name, + indexPattern, + workflows, + skipRedirect, + dataSourceInfo, +}: AddIntegrationRequestParams): Promise { const http = coreRefs.http!; if (addSample) { createIndexPatternMappings( - `ss4o_${integration.type}-${integrationTemplateId}-*-sample`, - integrationTemplateId, + `ss4o_${integration.type}-${templateName}-*-sample`, + templateName, integration, setToast ); - name = `${integrationTemplateId}-sample`; - indexPattern = `ss4o_${integration.type}-${integrationTemplateId}-sample-sample`; + name = `${templateName}-sample`; + indexPattern = `ss4o_${integration.type}-${templateName}-sample-sample`; } const createReqBody: { diff --git a/public/components/integrations/components/integration.tsx b/public/components/integrations/components/integration.tsx index 6bccdd65ee..5120581618 100644 --- a/public/components/integrations/components/integration.tsx +++ b/public/components/integrations/components/integration.tsx @@ -23,14 +23,14 @@ import { INTEGRATIONS_BASE } from '../../../../common/constants/shared'; import { IntegrationScreenshots } from './integration_screenshots_panel'; import { useToast } from '../../../../public/components/common/toast'; import { coreRefs } from '../../../framework/core_refs'; -import { IntegrationTemplate, addIntegrationRequest } from './create_integration_helpers'; +import { addIntegrationRequest } from './create_integration_helpers'; export function Integration(props: AvailableIntegrationProps) { const http = coreRefs.http!; const { integrationTemplateId, chrome } = props; const { setToast } = useToast(); - const [integration, setIntegration] = useState({} as IntegrationTemplate); + const [integration, setIntegration] = useState({} as IntegrationConfig); const [integrationMapping, setMapping] = useState(null); const [integrationAssets, setAssets] = useState([]); @@ -149,13 +149,12 @@ export function Integration(props: AvailableIntegrationProps) { }} setUpSample={async () => { setLoading(true); - await addIntegrationRequest( - true, - integration.name, - integrationTemplateId, + await addIntegrationRequest({ + addSample: true, + templateName: integration.name, integration, - setToast - ); + setToast, + }); setLoading(false); }} loading={loading} diff --git a/public/components/integrations/components/setup_integration.tsx b/public/components/integrations/components/setup_integration.tsx index 3a78efa636..e521e3b74b 100644 --- a/public/components/integrations/components/setup_integration.tsx +++ b/public/components/integrations/components/setup_integration.tsx @@ -7,36 +7,23 @@ import { EuiBottomBar, EuiButton, EuiButtonEmpty, - EuiCallOut, - EuiCheckableCard, - EuiComboBox, EuiEmptyPrompt, - EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiFlyoutBody, EuiFlyoutFooter, - EuiForm, - EuiFormRow, EuiLoadingLogo, EuiPage, EuiPageBody, EuiPageContent, EuiPageContentBody, - EuiSelect, - EuiSpacer, - EuiText, - EuiTitle, } from '@elastic/eui'; import React, { useState, useEffect } from 'react'; import { Color } from '../../../../common/constants/integrations'; import { coreRefs } from '../../../framework/core_refs'; import { addIntegrationRequest } from './create_integration_helpers'; -import { - CONSOLE_PROXY, - INTEGRATIONS_BASE, - DATACONNECTIONS_BASE, -} from '../../../../common/constants/shared'; +import { SetupIntegrationFormInputs } from './setup_integration_inputs'; +import { CONSOLE_PROXY, INTEGRATIONS_BASE } from '../../../../common/constants/shared'; export interface IntegrationSetupInputs { displayName: string; @@ -48,9 +35,7 @@ export interface IntegrationSetupInputs { enabledWorkflows: string[]; } -type SetupCallout = { show: true; title: string; color?: Color; text?: string } | { show: false }; - -interface IntegrationConfigProps { +export interface IntegrationConfigProps { config: IntegrationSetupInputs; updateConfig: (updates: Partial) => void; integration: IntegrationConfig; @@ -58,81 +43,7 @@ interface IntegrationConfigProps { lockConnectionType?: boolean; } -// TODO support localization -const INTEGRATION_CONNECTION_DATA_SOURCE_TYPES: Map< - string, - { - title: string; - lower: string; - help: string; - } -> = new Map([ - [ - 's3', - { - title: 'Data Source', - lower: 'data_source', - help: 'Select a data source to pull the data from.', - }, - ], - [ - 'index', - { - title: 'Index', - lower: 'index', - help: 'Select an index to pull the data from.', - }, - ], -]); - -const integrationConnectionSelectorItems = [ - { - value: 's3', - text: 'S3 Connection', - }, - { - value: 'index', - text: 'OpenSearch Index', - }, -]; - -const suggestDataSources = async (type: string): Promise> => { - const http = coreRefs.http!; - try { - if (type === 'index') { - const result = await http.post(CONSOLE_PROXY, { - body: '{}', - query: { - path: '_data_stream/ss4o_*', - method: 'GET', - }, - }); - return ( - result.data_streams?.map((item: { name: string }) => { - return { label: item.name }; - }) ?? [] - ); - } else if (type === 's3') { - const result = (await http.get(DATACONNECTIONS_BASE)) as Array<{ - name: string; - connector: string; - }>; - return ( - result - ?.filter((item) => item.connector === 'S3GLUE') - .map((item) => { - return { label: item.name }; - }) ?? [] - ); - } else { - console.error(`Unknown connection type: ${type}`); - return []; - } - } catch (err) { - console.error(err.message); - return []; - } -}; +type SetupCallout = { show: true; title: string; color?: Color; text?: string } | { show: false }; const runQuery = async ( query: string, @@ -187,263 +98,26 @@ const runQuery = async ( } }; -export function SetupWorkflowSelector({ - integration, - useWorkflows, - toggleWorkflow, -}: { - integration: IntegrationConfig; - useWorkflows: Map; - toggleWorkflow: (name: string) => void; -}) { - if (!integration.workflows) { - return null; - } - - const cards = integration.workflows.map((workflow) => { - return ( - toggleWorkflow(workflow.name)} - > - {workflow.description} - - ); - }); - - return cards; -} - -export function SetupIntegrationFormInputs({ - config, - updateConfig, - integration, - setupCallout, - lockConnectionType, -}: IntegrationConfigProps) { - const connectionType = INTEGRATION_CONNECTION_DATA_SOURCE_TYPES.get(config.connectionType)!; - - const [dataSourceSuggestions, setDataSourceSuggestions] = useState( - [] as Array<{ label: string }> - ); - const [isSuggestionsLoading, setIsSuggestionsLoading] = useState(true); - const [isBucketBlurred, setIsBucketBlurred] = useState(false); - const [isCheckpointBlurred, setIsCheckpointBlurred] = useState(false); - - const [useWorkflows, setUseWorkflows] = useState(new Map()); - const toggleWorkflow = (name: string) => { - setUseWorkflows(new Map(useWorkflows.set(name, !useWorkflows.get(name)))); - }; - - useEffect(() => { - if (integration.workflows) { - setUseWorkflows(new Map(integration.workflows.map((w) => [w.name, w.enabled_by_default]))); - } - }, [integration.workflows]); - - useEffect(() => { - updateConfig({ - enabledWorkflows: [...useWorkflows.entries()].filter((w) => w[1]).map((w) => w[0]), - }); - // If we add the updateConfig dep here, rendering crashes with "Maximum update depth exceeded" - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [useWorkflows]); - - useEffect(() => { - const updateDataSources = async () => { - const data = await suggestDataSources(config.connectionType); - setDataSourceSuggestions(data); - setIsSuggestionsLoading(false); - }; - - setIsSuggestionsLoading(true); - updateDataSources(); - }, [config.connectionType]); - - return ( - - -

Set Up Integration

-
- - {setupCallout.show ? ( - -

{setupCallout.text}

-
- ) : null} - - -

Integration Details

-
- - - updateConfig({ displayName: event.target.value })} - placeholder={`${integration.name} Integration`} - isInvalid={config.displayName.length === 0} - data-test-subj="new-instance-name" - /> - - - -

Integration Connection

-
- - - { - if (item.value === 's3') { - return integration.assets.some((asset) => asset.type === 'query'); - } else if (item.value === 'index') { - return integration.assets.some((asset) => asset.type === 'savedObjectBundle'); - } else { - return false; - } - })} - value={config.connectionType} - onChange={(event) => - updateConfig({ connectionType: event.target.value, connectionDataSource: '' }) - } - disabled={lockConnectionType} - /> - - - { - if (selected.length === 0) { - updateConfig({ connectionDataSource: '' }); - } else { - updateConfig({ connectionDataSource: selected[0].label }); - } - }} - selectedOptions={[{ label: config.connectionDataSource }]} - singleSelection={{ asPlainText: true }} - onCreateOption={(searchValue) => { - const normalizedSearchValue = searchValue.trim(); - if (!normalizedSearchValue) { - return; - } - const newOption = { label: normalizedSearchValue }; - setDataSourceSuggestions((ds) => ds.concat([newOption])); - updateConfig({ connectionDataSource: newOption.label }); - }} - customOptionText={`Select {searchValue} as your ${connectionType.lower}`} - data-test-subj="data-source-name" - isDisabled={lockConnectionType} - /> - - {config.connectionType === 's3' ? ( - <> - - { - updateConfig({ connectionTableName: evt.target.value }); - }} - isInvalid={config.connectionTableName.length === 0} - /> - - - updateConfig({ connectionLocation: event.target.value })} - placeholder="s3://" - isInvalid={isBucketBlurred && !config.connectionLocation.startsWith('s3://')} - onBlur={() => { - setIsBucketBlurred(true); - }} - /> - - - updateConfig({ checkpointLocation: event.target.value })} - placeholder="s3://" - isInvalid={isCheckpointBlurred && !config.checkpointLocation.startsWith('s3://')} - onBlur={() => { - setIsCheckpointBlurred(true); - }} - /> - - {integration.workflows ? ( - <> - - -

Integration Resources

-
- - -

- This integration offers valuable resources compatible with your data source. - These can include dashboards, visualizations, indexes, and queries. Select at - least one of the following options. -

-
-
- - - - - - ) : null} - {/* Bottom bar will overlap content if there isn't some space at the end */} - - - - ) : null} -
- ); -} - const makeTableName = (config: IntegrationSetupInputs): string => { return `${config.connectionDataSource}.default.${config.connectionTableName}`; }; const prepareQuery = (query: string, config: IntegrationSetupInputs): string => { + // To prevent checkpoint collisions, each query needs a unique checkpoint name, we use an enriched + // UUID to create subfolders under the given checkpoint location per-query. + const querySpecificUUID = crypto.randomUUID(); + let checkpointLocation = config.checkpointLocation.endsWith('/') + ? config.checkpointLocation + : config.checkpointLocation + '/'; + checkpointLocation += `${config.connectionDataSource}-${config.connectionTableName}-${querySpecificUUID}`; + let queryStr = query.replaceAll('{table_name}', makeTableName(config)); queryStr = queryStr.replaceAll('{s3_bucket_location}', config.connectionLocation); - queryStr = queryStr.replaceAll('{s3_checkpoint_location}', config.checkpointLocation); + queryStr = queryStr.replaceAll('{s3_checkpoint_location}', checkpointLocation); queryStr = queryStr.replaceAll('{object_name}', config.connectionTableName); + // TODO spark API only supports single-line queries, but directly replacing all whitespace leads + // to issues with single-line comments and quoted strings with more whitespace. A more robust + // implementation would remove comments before flattening and ignore strings. queryStr = queryStr.replaceAll(/\s+/g, ' '); return queryStr; }; @@ -465,17 +139,15 @@ const addIntegration = async ({ let sessionId: string | null = null; if (config.connectionType === 'index') { - const res = await addIntegrationRequest( - false, - integration.name, - config.displayName, + const res = await addIntegrationRequest({ + addSample: false, + templateName: integration.name, integration, - setCalloutLikeToast, - config.displayName, - config.connectionDataSource, - undefined, - setIsInstalling ? true : false - ); + setToast: setCalloutLikeToast, + name: config.displayName, + indexPattern: config.connectionDataSource, + skipRedirect: setIsInstalling ? true : false, + }); if (setIsInstalling) { setIsInstalling(false, res); } @@ -508,18 +180,17 @@ const addIntegration = async ({ sessionId = result.value.sessionId ?? sessionId; } // Once everything is ready, add the integration to the new datasource as usual - const res = await addIntegrationRequest( - false, - integration.name, - config.displayName, + const res = await addIntegrationRequest({ + addSample: false, + templateName: integration.name, integration, - setCalloutLikeToast, - config.displayName, - `flint_${config.connectionDataSource}_default_${config.connectionTableName}__*`, - config.enabledWorkflows, - setIsInstalling ? true : false, - { dataSource: config.connectionDataSource, tableName: makeTableName(config) } - ); + setToast: setCalloutLikeToast, + name: config.displayName, + indexPattern: `flint_${config.connectionDataSource}_default_${config.connectionTableName}__*`, + workflows: config.enabledWorkflows, + skipRedirect: setIsInstalling ? true : false, + dataSourceInfo: { dataSource: config.connectionDataSource, tableName: makeTableName(config) }, + }); if (setIsInstalling) { setIsInstalling(false, res); } diff --git a/public/components/integrations/components/setup_integration_inputs.tsx b/public/components/integrations/components/setup_integration_inputs.tsx new file mode 100644 index 0000000000..77ff20963f --- /dev/null +++ b/public/components/integrations/components/setup_integration_inputs.tsx @@ -0,0 +1,436 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiCallOut, + EuiCheckableCard, + EuiComboBox, + EuiFieldText, + EuiForm, + EuiFormRow, + EuiSelect, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import React, { useState, useEffect } from 'react'; +import { coreRefs } from '../../../framework/core_refs'; +import { CONSOLE_PROXY, DATACONNECTIONS_BASE } from '../../../../common/constants/shared'; +import { IntegrationConfigProps, IntegrationSetupInputs } from './setup_integration'; + +// TODO support localization +const INTEGRATION_CONNECTION_DATA_SOURCE_TYPES: Map< + string, + { + title: string; + lower: string; + help: string; + } +> = new Map([ + [ + 's3', + { + title: 'Data Source', + lower: 'data_source', + help: 'Select a data source to pull the data from.', + }, + ], + [ + 'index', + { + title: 'Index', + lower: 'index', + help: 'Select an index to pull the data from.', + }, + ], +]); + +const integrationConnectionSelectorItems = [ + { + value: 's3', + text: 'S3 Connection', + }, + { + value: 'index', + text: 'OpenSearch Index', + }, +]; + +const suggestDataSources = async (type: string): Promise> => { + const http = coreRefs.http!; + try { + if (type === 'index') { + const result = await http.post(CONSOLE_PROXY, { + body: '{}', + query: { + path: '_data_stream/ss4o_*', + method: 'GET', + }, + }); + return ( + result.data_streams?.map((item: { name: string }) => { + return { label: item.name }; + }) ?? [] + ); + } else if (type === 's3') { + const result = (await http.get(DATACONNECTIONS_BASE)) as Array<{ + name: string; + connector: string; + }>; + return ( + result + ?.filter((item) => item.connector === 'S3GLUE') + .map((item) => { + return { label: item.name }; + }) ?? [] + ); + } else { + console.error(`Unknown connection type: ${type}`); + return []; + } + } catch (err) { + console.error(err.message); + return []; + } +}; + +export function SetupWorkflowSelector({ + integration, + useWorkflows, + toggleWorkflow, +}: { + integration: IntegrationConfig; + useWorkflows: Map; + toggleWorkflow: (name: string) => void; +}) { + if (!integration.workflows) { + return null; + } + + const cards = integration.workflows.map((workflow) => { + return ( + toggleWorkflow(workflow.name)} + > + {workflow.description} + + ); + }); + + return cards; +} + +export function IntegrationDetailsInputs({ + config, + updateConfig, + integration, +}: { + config: IntegrationSetupInputs; + updateConfig: (updates: Partial) => void; + integration: IntegrationConfig; +}) { + return ( + + updateConfig({ displayName: event.target.value })} + placeholder={`${integration.name} Integration`} + isInvalid={config.displayName.length === 0} + data-test-subj="new-instance-name" + /> + + ); +} + +export function IntegrationConnectionInputs({ + config, + updateConfig, + integration, + lockConnectionType, +}: { + config: IntegrationSetupInputs; + updateConfig: (updates: Partial) => void; + integration: IntegrationConfig; + lockConnectionType?: boolean; +}) { + const connectionType = INTEGRATION_CONNECTION_DATA_SOURCE_TYPES.get(config.connectionType)!; + + const [dataSourceSuggestions, setDataSourceSuggestions] = useState( + [] as Array<{ label: string }> + ); + const [isSuggestionsLoading, setIsSuggestionsLoading] = useState(true); + + useEffect(() => { + const updateDataSources = async () => { + const data = await suggestDataSources(config.connectionType); + setDataSourceSuggestions(data); + setIsSuggestionsLoading(false); + }; + + setIsSuggestionsLoading(true); + updateDataSources(); + }, [config.connectionType]); + + return ( + <> + + { + if (item.value === 's3') { + return integration.assets.some((asset) => asset.type === 'query'); + } else if (item.value === 'index') { + return integration.assets.some((asset) => asset.type === 'savedObjectBundle'); + } else { + return false; + } + })} + value={config.connectionType} + onChange={(event) => + updateConfig({ connectionType: event.target.value, connectionDataSource: '' }) + } + disabled={lockConnectionType} + /> + + + { + if (selected.length === 0) { + updateConfig({ connectionDataSource: '' }); + } else { + updateConfig({ connectionDataSource: selected[0].label }); + } + }} + selectedOptions={[{ label: config.connectionDataSource }]} + singleSelection={{ asPlainText: true }} + onCreateOption={(searchValue) => { + const normalizedSearchValue = searchValue.trim(); + if (!normalizedSearchValue) { + return; + } + const newOption = { label: normalizedSearchValue }; + setDataSourceSuggestions((ds) => ds.concat([newOption])); + updateConfig({ connectionDataSource: newOption.label }); + }} + customOptionText={`Select {searchValue} as your ${connectionType.lower}`} + data-test-subj="data-source-name" + isDisabled={lockConnectionType} + /> + + + ); +} + +export function IntegrationQueryInputs({ + config, + updateConfig, + integration, +}: { + config: IntegrationSetupInputs; + updateConfig: (updates: Partial) => void; + integration: IntegrationConfig; +}) { + const [isBucketBlurred, setIsBucketBlurred] = useState(false); + const [isCheckpointBlurred, setIsCheckpointBlurred] = useState(false); + + return ( + <> + + { + updateConfig({ connectionTableName: evt.target.value }); + }} + isInvalid={config.connectionTableName.length === 0} + /> + + + updateConfig({ connectionLocation: event.target.value })} + placeholder="s3://" + isInvalid={isBucketBlurred && !config.connectionLocation.startsWith('s3://')} + onBlur={() => { + setIsBucketBlurred(true); + }} + /> + + + updateConfig({ checkpointLocation: event.target.value })} + placeholder="s3://" + isInvalid={isCheckpointBlurred && !config.checkpointLocation.startsWith('s3://')} + onBlur={() => { + setIsCheckpointBlurred(true); + }} + /> + + + ); +} + +export function IntegrationWorkflowsInputs({ + updateConfig, + integration, +}: { + updateConfig: (updates: Partial) => void; + integration: IntegrationConfig; +}) { + const [useWorkflows, setUseWorkflows] = useState(new Map()); + const toggleWorkflow = (name: string) => { + setUseWorkflows((currentWorkflows) => { + const newWorkflows = new Map(currentWorkflows); + newWorkflows.set(name, !newWorkflows.get(name)); + return newWorkflows; + }); + }; + + useEffect(() => { + if (integration.workflows) { + setUseWorkflows(new Map(integration.workflows.map((w) => [w.name, w.enabled_by_default]))); + } + }, [integration.workflows]); + + useEffect(() => { + updateConfig({ + enabledWorkflows: [...useWorkflows.entries()].filter((w) => w[1]).map((w) => w[0]), + }); + // If we add the updateConfig dep here, rendering crashes with "Maximum update depth exceeded" + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [useWorkflows]); + + return ( + + + + ); +} + +export function SetupIntegrationFormInputs({ + config, + updateConfig, + integration, + setupCallout, + lockConnectionType, +}: IntegrationConfigProps) { + return ( + + +

Set Up Integration

+
+ + {setupCallout.show ? ( + +

{setupCallout.text}

+
+ ) : null} + + +

Integration Details

+
+ + + + +

Integration Connection

+
+ + + {config.connectionType === 's3' ? ( + <> + + +

Query Fields

+
+ + +

+ To set up the integration, we need to know some information about how to process + your data. +

+
+
+ + + {integration.workflows ? ( + <> + + +

Integration Resources

+
+ + +

+ This integration offers different kinds of resources compatible with your data + source. These can include dashboards, visualizations, indexes, and queries. + Select at least one of the following options. +

+
+
+ + + + ) : null} + {/* Bottom bar will overlap content if there isn't some space at the end */} + + + + ) : null} +
+ ); +} diff --git a/public/services/data_fetchers/ppl/ppl_data_fetcher.ts b/public/services/data_fetchers/ppl/ppl_data_fetcher.ts index 119747ba02..fc299218d7 100644 --- a/public/services/data_fetchers/ppl/ppl_data_fetcher.ts +++ b/public/services/data_fetchers/ppl/ppl_data_fetcher.ts @@ -23,7 +23,10 @@ import { TAB_CHART_ID, } from '../../../../common/constants/explorer'; import { PPL_STATS_REGEX } from '../../../../common/constants/shared'; -import { composeFinalQueryWithoutTimestamp } from '../../../components/common/query_utils'; +import { + composeFinalQueryWithoutTimestamp, + getDescribeQueryIndexFromRawQuery, +} from '../../../components/common/query_utils'; export class PPLDataFetcher extends DataFetcherBase implements IDataFetcher { protected queryIndex: string; @@ -70,6 +73,12 @@ export class PPLDataFetcher extends DataFetcherBase implements IDataFetcher { this.queryIndex = this.getIndex(buildRawQuery(query, appBaseQuery)); + // check for describe command and execute if possible + const describeQueryIndex = getDescribeQueryIndexFromRawQuery( + buildRawQuery(query, appBaseQuery) + ); + if (describeQueryIndex) this.queryIndex = describeQueryIndex; + if (this.queryIndex === '') return; // Returns if page is refreshed const { @@ -87,7 +96,7 @@ export class PPLDataFetcher extends DataFetcherBase implements IDataFetcher { await this.processTimestamp(query); - const noTimestamp = isEmpty(this.timestamp); + const noTimestamp = isEmpty(this.timestamp) || !!describeQueryIndex; const curStartTime = noTimestamp ? undefined diff --git a/server/adaptors/integrations/__data__/repository/aws_vpc_flow/assets/create_skipping_index-1.0.0.sql b/server/adaptors/integrations/__data__/repository/aws_vpc_flow/assets/create_skipping_index-1.0.0.sql new file mode 100644 index 0000000000..977af0b958 --- /dev/null +++ b/server/adaptors/integrations/__data__/repository/aws_vpc_flow/assets/create_skipping_index-1.0.0.sql @@ -0,0 +1,16 @@ +CREATE SKIPPING INDEX ON {table_name} ( + accountid BLOOM_FILTER, + region VALUE_SET, + severity_id VALUE_SET, + src_endpoint.ip BLOOM_FILTER, + dst_endpoint.ip BLOOM_FILTER, + src_endpoint.svc_name VALUE_SET, + dst_endpoint.svc_name VALUE_SET, + request_processing_time MIN_MAX, + traffic.bytes MIN_MAX +) WITH ( + auto_refresh = true, + refresh_interval = '15 Minutes', + checkpoint_location = '{s3_checkpoint_location}', + watermark_delay = '1 Minute' +) diff --git a/server/adaptors/integrations/__data__/repository/aws_vpc_flow/assets/example_queries-1.0.0.ndjson b/server/adaptors/integrations/__data__/repository/aws_vpc_flow/assets/example_queries-1.0.0.ndjson new file mode 100644 index 0000000000..7c27b42bb6 --- /dev/null +++ b/server/adaptors/integrations/__data__/repository/aws_vpc_flow/assets/example_queries-1.0.0.ndjson @@ -0,0 +1,4 @@ +{"attributes":{"createdTimeMs":1713289099101,"savedQuery":{"data_sources":"[{\"name\":\"mys3\",\"type\":\"s3glue\",\"label\":\"mys3\",\"value\":\"mys3\"}]","description":"","name":"agg_60_min_connections_view","query":"SELECT date_trunc('hour', from_unixtime(start_time / 1000)) AS interval_start_time, date_trunc('hour', from_unixtime(start_time / 1000)) + INTERVAL 1 HOUR AS interval_end_time, accountid as `aws.vpc.account-id`, region as `aws.vpc.region`, COUNT(*) AS total_connections, SUM(CAST(IFNULL(traffic.bytes, 0) AS LONG)) AS total_bytes, SUM(CAST(IFNULL(traffic.packets, 0) AS LONG)) AS total_packets FROM {table_name} GROUP BY date_trunc('hour', from_unixtime(start_time / 1000)), region, accountid\n","query_lang":"SQL","selected_date_range":{"end":"now","start":"now-15m","text":""},"selected_fields":{"text":"","tokens":[]},"selected_timestamp":{"name":"","type":"timestamp"}},"title":"Hourly count connections summary","version":1},"id":"1d07d010-fc18-11ee-99c9-43e5dbd0692c","references":[],"type":"observability-search","updated_at":"2024-04-16T17:52:30.414Z","version":"WzI3NTEsMV0="} +{"attributes":{"createdTimeMs":1713293044079,"savedQuery":{"data_sources":"[{\"name\":\"mys3\",\"type\":\"s3glue\",\"label\":\"mys3\",\"value\":\"mys3\"}]","description":"","name":"window_agg_60_min_network_ip_bytes","query":"WITH hourly_buckets AS ( SELECT date_trunc('hour', from_unixtime(start_time / 1000)) AS interval_start_time, CAST(IFNULL(dst_endpoint.ip, '0.0.0.0') AS STRING) AS dstaddr, SUM(CAST(IFNULL(traffic.bytes, 0) AS LONG)) AS total_bytes FROM {table_name} GROUP BY interval_start_time, dstaddr ), ranked_addresses AS ( SELECT CAST(interval_start_time AS TIMESTAMP), dstaddr, total_bytes, RANK() OVER (PARTITION BY interval_start_time ORDER BY total_bytes DESC) AS bytes_rank FROM hourly_buckets ) SELECT CAST(interval_start_time AS TIMESTAMP), dstaddr, total_bytes FROM ranked_addresses WHERE bytes_rank <= 50 ORDER BY interval_start_time ASC, bytes_rank ASC","query_lang":"SQL","selected_date_range":{"end":"now","start":"now-15m","text":""},"selected_fields":{"text":"","tokens":[]},"selected_timestamp":{"name":"","type":"timestamp"}},"title":"window hourly network ip bytes summary","version":1},"id":"4c6b8820-fc21-11ee-ab45-d3075d0510e6","references":[],"type":"observability-search","updated_at":"2024-04-16T18:44:47.956Z","version":"WzI4MzAsMV0="} +{"attributes":{"createdTimeMs":1713290175184,"savedQuery":{"data_sources":"[{\"name\":\"mys3\",\"type\":\"s3glue\",\"label\":\"mys3\",\"value\":\"mys3\"}]","description":"","name":"TopCommonErrorServicesQuery","query":"SELECT src_endpoint.svc_name AS source_service, dst_endpoint.svc_name AS destination_service, COUNT(*) AS error_count FROM {table_name} WHERE severity_id >= 4 GROUP BY src_endpoint.svc_name, dst_endpoint.svc_name ORDER BY error_count DESC LIMIT 10;\n","query_lang":"SQL","selected_date_range":{"end":"now","start":"now-15m","text":""},"selected_fields":{"text":"","tokens":[]},"selected_timestamp":{"name":"","type":"timestamp"}},"title":"Top 10 pairs of errored source / destination services","version":1},"id":"9e6a9b40-fc1a-11ee-99c9-43e5dbd0692c","references":[],"type":"observability-search","updated_at":"2024-04-16T17:56:15.220Z","version":"WzI3NTIsMV0="} +{"exportedCount":7,"missingRefCount":0,"missingReferences":[]} diff --git a/server/adaptors/integrations/__data__/repository/aws_vpc_flow/aws_vpc_flow-1.0.0.json b/server/adaptors/integrations/__data__/repository/aws_vpc_flow/aws_vpc_flow-1.0.0.json index 5862571ce5..53eea4118a 100644 --- a/server/adaptors/integrations/__data__/repository/aws_vpc_flow/aws_vpc_flow-1.0.0.json +++ b/server/adaptors/integrations/__data__/repository/aws_vpc_flow/aws_vpc_flow-1.0.0.json @@ -75,6 +75,27 @@ } ], "assets": [ + { + "name": "create_table_parquet_vpc", + "version": "1.0.0", + "extension": "sql", + "type": "query", + "workflows": ["flint-live-dashboards","flint-pre-agg-dashboards"] + }, + { + "name": "example_queries", + "version": "1.0.0", + "extension": "ndjson", + "type": "savedObjectBundle", + "workflows": ["queries"] + }, + { + "name": "create_skipping_index", + "version": "1.0.0", + "extension": "sql", + "type": "query", + "workflows": ["queries"] + }, { "name": "aws_vpc_flow", "version": "1.0.0", @@ -96,14 +117,6 @@ "type": "savedObjectBundle", "workflows": ["flint-pre-agg-dashboards"] }, - - { - "name": "create_table_parquet_vpc", - "version": "1.0.0", - "extension": "sql", - "type": "query", - "workflows": ["flint-live-dashboards","flint-pre-agg-dashboards"] - }, { "name": "vpc_live_all_mv", "version": "1.0.0", @@ -139,7 +152,6 @@ "type": "query", "workflows": ["flint-pre-agg-dashboards"] }, - { "name": "vpc_live_week_refresh", "version": "1.0.0",