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
-
-
-
-
-
+ Display Name
+
+
+
+
-
-
+
+
+
-
-
+
+
-
-
-
-
- Connection Type
-
-
-
-
-
+ Connection Type
+
+
+
+
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
- Select the type of connection to use for queries.
-
-
+
+ Select the type of connection to use for queries.
+
+
+
-
-
-
-
+
-
-
- Index
-
-
-
-
-
+ 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
-
-
-
-
-
-
-
-
-
-
-
- Display Name
-
-
-
-
-
-
-
-
-
-
-
-
- Integration Connection
-
-
-
-
-
-
-
-
-
-
-
- Connection Type
-
-
-
-
-
-
-
-
-
-
-
- OpenSearch Index
-
-
-
-
-
-
-
-
-
-
-
-
- Select the type of connection to use for queries.
-
-
-
-
-
-
-
-
-
-
- Data Source
-
-
-
-
-
-
-
-
-
-
-
-
-
- ss4o_logs-nginx-test
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Select a data source to pull the data from.
-
-
-
-
-
-
-
-
-
-
- Flint Table Name
-
-
-
-
-
-
-
-
-
-
-
- Must be at least 1 character.
-
-
-
-
- Select a table name to associate with your data.
-
-
-
-
-
-
-
-
-
-
- S3 Bucket Location
-
-
-
-
-
-
-
-
-
-
-
- S3 Checkpoint Location
-
-
-
-
-
-
-
-
-
-
-
- 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>
-
-
-
- Workflow 1
-
-
- This is a test workflow.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Integration Setup Page Renders the S3 connector form without workflows 1`] = `
-
-
-
-
-
- Set Up Integration
-
-
-
-
-
-
-
-
-
-
-
- Integration Details
-
-
-
-
-
-
-
-
-
-
-
- Display Name
-
-
-
-
-
-
-
-
-
-
-
-
- Integration Connection
-
-
-
-
-
-
-
-
-
-
-
- Connection Type
-
-
-
-
-
-
-
-
-
-
-
- OpenSearch Index
-
-
-
-
-
-
-
-
-
-
-
-
- Select the type of connection to use for queries.
-
-
-
-
-
-
-
-
-
-
- Data Source
-
-
-
-
-
-
-
-
-
-
-
-
-
- ss4o_logs-nginx-test
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Select a data source to pull the data from.
-
-
-
-
-
-
-
-
-
-
- Flint Table Name
-
-
-
-
-
-
-
-
-
-
-
- Must be at least 1 character.
-
-
-
-
- Select a table name to associate with your data.
-
-
-
-
-
-
-
-
-
-
- S3 Bucket Location
-
-
-
-
-
-
-
-
-
-
-
- S3 Checkpoint Location
-
-
-
-
-
-
-
-
-
-
-
- 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
-
-
-
-
-
-
-
-
-
-
-
- Display Name
-
-
-
-
-
-
-
-
-
-
-
-
- Integration Connection
-
-
-
-
-
-
-
-
-
-
-
- Connection Type
-
-
-
-
-
-
-
-
-
-
-
- OpenSearch Index
-
-
-
-
-
-
-
-
-
-
-
-
- Select the type of connection to use for queries.
-
-
-
-
-
-
-
-
-
-
- Index
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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`] = `
+
+
+
+
+
+
+ Connection Type
+
+
+
+
+
+
+
+
+
+
+
+ OpenSearch Index
+
+
+
+
+
+
+
+
+
+
+
+
+ Select the type of connection to use for queries.
+
+
+
+
+
+
+
+
+
+
+ Data Source
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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`] = `
+
+
+
+
+
+
+ Connection Type
+
+
+
+
+
+
+
+
+
+
+
+ OpenSearch Index
+
+
+
+
+
+
+
+
+
+
+
+
+ Select the type of connection to use for queries.
+
+
+
+
+
+
+
+
+
+
+ Data Source
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ss4o_logs-nginx-test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Select a data source to pull the data from.
+
+
+
+
+
+
+`;
+
+exports[`Integration Setup Inputs Renders the details inputs 1`] = `
+
+
+
+
+
+
+ Display Name
+
+
+
+
+
+
+
+`;
+
+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`] = `
+
+
+
+
+
+
+ Spark Table Name
+
+
+
+
+
+
+
+
+
+
+
+ Must be at least 1 character.
+
+
+
+
+ Select a table name to associate with your data.
+
+
+
+
+
+
+
+
+
+
+ S3 Data Location
+
+
+
+
+
+
+
+
+
+
+
+ S3 Checkpoint Location
+
+
+
+
+
+
+
+
+
+
+
+ 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>
+
+
+
+ Workflow 1
+
+
+ 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",