('kuery');
const { dataView } = useAlertsDataView({
featureIds,
http,
toasts,
- dataViewsService,
+ dataViewsService: dataService.dataViews,
});
const { aadFields, loading: fieldsLoading } = useRuleAADFields({
ruleTypeId,
@@ -119,7 +119,10 @@ export const AlertsSearchBar = ({
displayStyle: 'inPage',
showFilterBar,
onQuerySubmit: onSearchQuerySubmit,
- onFiltersUpdated,
+ onFiltersUpdated: (newFilters) => {
+ dataService.query.filterManager.setFilters(newFilters);
+ onFiltersUpdated?.(newFilters);
+ },
onRefresh,
showDatePicker,
showQueryInput: true,
diff --git a/packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts b/packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts
index 8875ef62947a84..f8566b2ec692cc 100644
--- a/packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts
+++ b/packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts
@@ -11,7 +11,7 @@ import type { Filter } from '@kbn/es-query';
import type { ValidFeatureId } from '@kbn/rule-data-utils';
import type { ToastsStart, HttpStart } from '@kbn/core/public';
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
-import type { DataViewsContract } from '@kbn/data-views-plugin/common';
+import { DataPublicPluginStart } from '@kbn/data-plugin/public';
export type QueryLanguageType = 'lucene' | 'kuery';
@@ -41,5 +41,5 @@ export interface AlertsSearchBarProps {
http: HttpStart;
toasts: ToastsStart;
unifiedSearchBar: UnifiedSearchPublicPluginStart['ui']['SearchBar'];
- dataViewsService: DataViewsContract;
+ dataService: DataPublicPluginStart;
}
diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.test.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.test.tsx
index d3ef68206cfa46..9b7502add4045e 100644
--- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.test.tsx
+++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.test.tsx
@@ -9,12 +9,12 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { httpServiceMock } from '@kbn/core/public/mocks';
-import { RuleActionsAlertsFilter } from './rule_actions_alerts_filter';
-import type { AlertsSearchBarProps } from '../../alerts_search_bar';
import { FilterStateStore } from '@kbn/es-query';
+import { AlertsSearchBarProps, AlertsSearchBar } from '../../alerts_search_bar';
import { getAction } from '../../common/test_utils/actions_test_utils';
-import userEvent from '@testing-library/user-event';
+import { RuleActionsAlertsFilter } from './rule_actions_alerts_filter';
const http = httpServiceMock.createStartContract();
@@ -23,43 +23,7 @@ jest.mock('../hooks', () => ({
}));
jest.mock('../../alerts_search_bar', () => ({
- AlertsSearchBar: ({ onFiltersUpdated, onQueryChange, onQuerySubmit }: AlertsSearchBarProps) => (
-
- AlertsSearchBar
-
-
-
-
- ),
+ AlertsSearchBar: jest.fn(),
}));
const { useRuleFormState } = jest.requireMock('../hooks');
@@ -79,7 +43,7 @@ describe('ruleActionsAlertsFilter', () => {
SearchBar: {},
},
},
- dataViews: {},
+ data: {},
},
formData: {
actions: [],
@@ -93,6 +57,46 @@ describe('ruleActionsAlertsFilter', () => {
});
});
+ (AlertsSearchBar as jest.Mock).mockImplementation(
+ ({ onFiltersUpdated, onQueryChange, onQuerySubmit }: AlertsSearchBarProps) => (
+
+ AlertsSearchBar
+
+
+
+
+ )
+ );
+
afterEach(() => {
jest.clearAllMocks();
});
@@ -166,18 +170,86 @@ describe('ruleActionsAlertsFilter', () => {
await userEvent.click(screen.getByText('Update Filter'));
expect(mockOnChange).toHaveBeenLastCalledWith({
- filters: [{ $state: { store: 'appState' }, meta: {} }],
+ filters: [{ $state: { store: 'appState' }, meta: {}, query: {} }],
kql: 'test',
});
await userEvent.click(screen.getByText('Update Query'));
expect(mockOnChange).toHaveBeenLastCalledWith({
- filters: [{ $state: { store: 'appState' }, meta: {} }],
+ filters: [{ $state: { store: 'appState' }, meta: {}, query: {} }],
kql: 'onQueryChange',
});
await userEvent.click(screen.getByText('Submit Query'));
expect(mockOnChange).toHaveBeenLastCalledWith({
- filters: [{ $state: { store: 'appState' }, meta: {} }],
+ filters: [{ $state: { store: 'appState' }, meta: {}, query: {} }],
kql: 'onQuerySubmit',
});
});
+
+ test('renders filters correctly', async () => {
+ const filters = [
+ {
+ meta: {
+ negate: false,
+ alias: null,
+ disabled: false,
+ type: 'custom',
+ key: 'query',
+ },
+ query: { bool: { filter: [{ term: { 'kibana.alert.rule.consumer': 'stackAlerts' } }] } },
+ $state: { store: FilterStateStore.APP_STATE },
+ },
+ ];
+
+ (AlertsSearchBar as jest.Mock).mockImplementation(
+ ({ onFiltersUpdated, onQueryChange, onQuerySubmit }: AlertsSearchBarProps) => (
+
+ AlertsSearchBar
+
+
+
+
+ )
+ );
+
+ render(
+
+ );
+
+ await userEvent.click(screen.getByTestId('alertsFilterQueryToggle'));
+
+ expect(mockOnChange).toHaveBeenLastCalledWith(undefined);
+
+ await userEvent.click(screen.getByText('Update Filter'));
+ expect(mockOnChange).toHaveBeenLastCalledWith({ filters, kql: '' });
+ });
});
diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.tsx
index 646d1b359969bc..791c1ce0491f28 100644
--- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.tsx
+++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.tsx
@@ -31,7 +31,7 @@ export interface RuleActionsAlertsFilterProps {
http: RuleFormPlugins['http'];
notifications: RuleFormPlugins['notifications'];
unifiedSearch: RuleFormPlugins['unifiedSearch'];
- dataViews: RuleFormPlugins['dataViews'];
+ data: RuleFormPlugins['data'];
};
}
@@ -48,7 +48,7 @@ export const RuleActionsAlertsFilter = ({
http,
notifications: { toasts },
unifiedSearch,
- dataViews,
+ data,
} = propsPlugins || plugins;
const [query, setQuery] = useState(action.alertsFilter?.query ?? DEFAULT_QUERY);
@@ -84,7 +84,19 @@ export const RuleActionsAlertsFilter = ({
);
const onFiltersUpdated = useCallback(
- (filters: Filter[]) => updateQuery({ filters }),
+ (filters: Filter[]) => {
+ const updatedFilters = filters.map((filter) => {
+ const { $state, meta, ...rest } = filter;
+ return {
+ $state,
+ meta,
+ query: filter?.query ? { ...filter.query } : { ...rest },
+ };
+ });
+
+ // Wrapping filters in query object here to avoid schema validation failure
+ updateQuery({ filters: updatedFilters });
+ },
[updateQuery]
);
@@ -108,7 +120,7 @@ export const RuleActionsAlertsFilter = ({
http={http}
toasts={toasts}
unifiedSearchBar={unifiedSearch.ui.SearchBar}
- dataViewsService={dataViews}
+ dataService={data}
appName={appName}
featureIds={featureIds}
ruleTypeId={ruleTypeId}
diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx
index 6783289963f6c7..7f3c25eeaf55d0 100644
--- a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx
+++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx
@@ -117,6 +117,17 @@ export const CreateMaintenanceWindowForm = React.memo {
+ return filtersToTransform.map((filter) => {
+ const { $state, meta, ...rest } = filter;
+ return {
+ $state,
+ meta,
+ query: filter?.query ? { ...filter.query } : { ...rest },
+ };
+ });
+ };
+
const scopedQueryPayload = useMemo(() => {
if (!isScopedQueryEnabled) {
return null;
@@ -124,9 +135,13 @@ export const CreateMaintenanceWindowForm = React.memo
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts
index 97c9ba686435a9..df1dce08f684ab 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts
@@ -11,9 +11,10 @@ import {
isValidUrl,
getConnectorWithInvalidatedFields,
getRuleWithInvalidatedFields,
+ validateActionFilterQuery,
} from './value_validators';
import { v4 as uuidv4 } from 'uuid';
-import { Rule, IErrorObject, UserConfiguredActionConnector } from '../../types';
+import { Rule, IErrorObject, UserConfiguredActionConnector, RuleUiAction } from '../../types';
describe('throwIfAbsent', () => {
test('throws if value is absent', () => {
@@ -441,3 +442,70 @@ describe('getRuleWithInvalidatedFields', () => {
expect((rule.actions[1].params as any).incident.field.name).toEqual('myIncident');
});
});
+
+describe('validateActionFilterQuery', () => {
+ test('does not return an error when kql query exists', () => {
+ const actionItem: RuleUiAction = {
+ actionTypeId: 'test',
+ group: 'qwer',
+ id: '123',
+ params: {
+ incident: {
+ field: {
+ name: 'myIncident',
+ },
+ },
+ },
+ alertsFilter: { query: { kql: 'id: *', filters: [] } },
+ };
+
+ expect(validateActionFilterQuery(actionItem)).toBe(null);
+ });
+
+ test('does not return an error when filter query exists', () => {
+ const actionItem = {
+ actionTypeId: 'test',
+ group: 'qwer',
+ id: '123',
+ params: {
+ incident: {
+ field: {
+ name: 'myIncident',
+ },
+ },
+ },
+ alertsFilter: {
+ query: {
+ kql: undefined,
+ filters: [
+ {
+ $state: { store: 'state' },
+ meta: { key: 'test' },
+ query: { exists: { field: '_id' } },
+ },
+ ],
+ },
+ },
+ };
+
+ expect(validateActionFilterQuery(actionItem)).toBe(null);
+ });
+
+ test('returns an error no kql and no filters', () => {
+ const actionItem = {
+ actionTypeId: 'test',
+ group: 'qwer',
+ id: '123',
+ params: {
+ incident: {
+ field: {
+ name: 'myIncident',
+ },
+ },
+ },
+ alertsFilter: { query: { kql: '', filters: [] } },
+ };
+
+ expect(validateActionFilterQuery(actionItem)).toBe('A custom query is required.');
+ });
+});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx
index 9176d9d54ef3af..9af97b713dff3e 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx
@@ -157,7 +157,7 @@ export const ActionTypeForm = ({
http,
notifications,
unifiedSearch,
- dataViews,
+ data,
} = useKibana().services;
const { euiTheme } = useEuiTheme();
const [isOpen, setIsOpen] = useState(true);
@@ -513,7 +513,7 @@ export const ActionTypeForm = ({
plugins={{
http,
unifiedSearch,
- dataViews,
+ data,
notifications,
}}
/>
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.test.tsx
new file mode 100644
index 00000000000000..61fceacead3413
--- /dev/null
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.test.tsx
@@ -0,0 +1,190 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { useAlertsDataView } from '@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view';
+import { Filter, FilterStateStore } from '@kbn/es-query';
+import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
+import { NotificationsStart } from '@kbn/core-notifications-browser';
+import { useKibana } from '@kbn/kibana-react-plugin/public';
+import { useLoadRuleTypesQuery } from '../../hooks/use_load_rule_types_query';
+import { useRuleAADFields } from '../../hooks/use_rule_aad_fields';
+import { AlertsSearchBar } from './alerts_search_bar';
+
+const mockDataPlugin = dataPluginMock.createStartContract();
+jest.mock('@kbn/kibana-utils-plugin/public');
+jest.mock('../../hooks/use_load_rule_types_query');
+jest.mock('../../hooks/use_rule_aad_fields');
+jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view');
+jest.mock('@kbn/kibana-react-plugin/public', () => ({
+ ...jest.requireActual('@kbn/kibana-react-plugin/public'),
+ useKibana: jest.fn(),
+}));
+
+jest.mocked(useAlertsDataView).mockReturnValue({
+ isLoading: false,
+ dataView: {
+ title: '.alerts-*',
+ fields: [
+ {
+ name: 'event.action',
+ type: 'string',
+ aggregatable: true,
+ searchable: true,
+ },
+ ],
+ },
+});
+
+jest.mocked(useLoadRuleTypesQuery).mockReturnValue({
+ ruleTypesState: {
+ initialLoad: false,
+ data: new Map(),
+ isLoading: false,
+ error: undefined,
+ },
+ authorizedToReadAnyRules: false,
+ hasAnyAuthorizedRuleType: false,
+ authorizedRuleTypes: [],
+ authorizedToCreateAnyRules: false,
+ isSuccess: false,
+});
+
+jest.mocked(useRuleAADFields).mockReturnValue({
+ aadFields: [],
+ loading: false,
+});
+
+const mockUseKibana = useKibana as jest.Mock;
+
+describe('AlertsSearchBar', () => {
+ beforeEach(() => {
+ mockUseKibana.mockReturnValue({
+ services: {
+ data: mockDataPlugin,
+ unifiedSearch: {
+ ui: {
+ SearchBar: jest.fn().mockImplementation((props) => (
+
+ )),
+ },
+ },
+ notifications: { toasts: { addWarning: jest.fn() } } as unknown as NotificationsStart,
+ },
+ });
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders correctly', async () => {
+ render(
+
+ );
+ expect(await screen.findByTestId('querySubmitButton')).toBeInTheDocument();
+ });
+
+ it('calls onQuerySubmit correctly', async () => {
+ const onQuerySubmitMock = jest.fn();
+
+ render(
+
+ );
+
+ fireEvent.click(await screen.findByTestId('querySubmitButton'));
+
+ await waitFor(() => {
+ expect(onQuerySubmitMock).toHaveBeenCalled();
+ });
+ });
+
+ it('calls onFiltersUpdated correctly', async () => {
+ const onFiltersUpdatedMock = jest.fn();
+ const filters: Filter[] = [
+ {
+ meta: {
+ negate: false,
+ alias: null,
+ disabled: false,
+ type: 'custom',
+ key: 'query',
+ },
+ query: { bool: { filter: [{ term: { 'kibana.alert.rule.consumer': 'stackAlerts' } }] } },
+ $state: { store: FilterStateStore.APP_STATE },
+ },
+ ];
+
+ mockUseKibana.mockReturnValue({
+ services: {
+ data: mockDataPlugin,
+ unifiedSearch: {
+ ui: {
+ SearchBar: jest.fn().mockImplementation((props) => (
+
+ )),
+ },
+ },
+ notifications: { toasts: { addWarning: jest.fn() } } as unknown as NotificationsStart,
+ },
+ });
+
+ render(
+
+ );
+
+ fireEvent.click(await screen.findByTestId('filtersSubmitButton'));
+
+ await waitFor(() => {
+ expect(onFiltersUpdatedMock).toHaveBeenCalledWith(filters);
+ expect(mockDataPlugin.query.filterManager.setFilters).toHaveBeenCalledWith(filters);
+ });
+ });
+});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx
index 3896e5d0e938a7..5e2880e65231c7 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx
@@ -53,6 +53,7 @@ export function AlertsSearchBar({
unifiedSearch: {
ui: { SearchBar },
},
+ data: dataService,
} = useKibana().services;
const [queryLanguage, setQueryLanguage] = useState('kuery');
@@ -183,7 +184,11 @@ export function AlertsSearchBar({
displayStyle="inPage"
showFilterBar={showFilterBar}
onQuerySubmit={onSearchQuerySubmit}
- onFiltersUpdated={onFiltersUpdated}
+ onFiltersUpdated={(newFilters) => {
+ const mappedFilters = structuredClone(newFilters);
+ dataService.query.filterManager.setFilters(mappedFilters);
+ onFiltersUpdated?.(mappedFilters);
+ }}
onRefresh={onRefresh}
showDatePicker={showDatePicker}
showQueryInput={true}
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts
index bf4a81ea4772d0..3c39bd235bf97b 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts
@@ -25,6 +25,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const toasts = getService('toasts');
const esClient = getService('es');
const apmSynthtraceKibanaClient = getService('apmSynthtraceKibanaClient');
+ const filterBar = getService('filterBar');
+ const esArchiver = getService('esArchiver');
async function getAlertsByName(name: string) {
const {
@@ -95,6 +97,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
describe('create alert', function () {
let apmSynthtraceEsClient: ApmSynthtraceEsClient;
before(async () => {
+ await esArchiver.load(
+ 'test/api_integration/fixtures/es_archiver/index_patterns/constant_keyword'
+ );
const version = (await apmSynthtraceKibanaClient.installApmPackage()).version;
apmSynthtraceEsClient = await getApmSynthtraceEsClient({
client: esClient,
@@ -130,7 +135,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
return Promise.all([apmSynthtraceEsClient.index(events)]);
});
- after(() => apmSynthtraceEsClient.clean());
+ after(async () => {
+ await apmSynthtraceEsClient.clean();
+ await esArchiver.unload(
+ 'test/api_integration/fixtures/es_archiver/index_patterns/constant_keyword'
+ );
+ });
beforeEach(async () => {
await pageObjects.common.navigateToApp('triggersActions');
@@ -344,6 +354,57 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id));
});
+ it('should create an alert with DSL filter for conditional action', async () => {
+ const alertName = generateUniqueKey();
+ await rules.common.defineIndexThresholdAlert(alertName);
+
+ // filterKuery validation
+ await testSubjects.setValue('filterKuery', 'group:');
+ const filterKueryInput = await testSubjects.find('filterKuery');
+ expect(await filterKueryInput.elementHasClass('euiFieldSearch-isInvalid')).to.eql(true);
+ await testSubjects.setValue('filterKuery', 'group: group-0');
+ expect(await filterKueryInput.elementHasClass('euiFieldSearch-isInvalid')).to.eql(false);
+
+ await testSubjects.click('.slack-alerting-ActionTypeSelectOption');
+ await testSubjects.click('addNewActionConnectorButton-.slack');
+ const slackConnectorName = generateUniqueKey();
+ await testSubjects.setValue('nameInput', slackConnectorName);
+ await testSubjects.setValue('slackWebhookUrlInput', 'https://test.com');
+ await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)');
+ const createdConnectorToastTitle = await toasts.getTitleAndDismiss();
+ expect(createdConnectorToastTitle).to.eql(`Created '${slackConnectorName}'`);
+ await testSubjects.click('notifyWhenSelect');
+ await testSubjects.click('onThrottleInterval');
+ await testSubjects.setValue('throttleInput', '10');
+
+ await testSubjects.click('alertsFilterQueryToggle');
+
+ await pageObjects.header.waitUntilLoadingHasFinished();
+
+ const filter = `{
+ "bool": {
+ "filter": [{ "term": { "kibana.alert.rule.consumer": "*" } }]
+ }
+ }`;
+ await filterBar.addDslFilter(filter, true);
+
+ await testSubjects.click('saveRuleButton');
+
+ const toastTitle = await toasts.getTitleAndDismiss();
+ expect(toastTitle).to.eql(`Created rule "${alertName}"`);
+
+ await testSubjects.click('editActionHoverButton');
+ await pageObjects.header.waitUntilLoadingHasFinished();
+
+ await testSubjects.scrollIntoView('globalQueryBar');
+
+ await filterBar.hasFilter('query', filter, true);
+
+ // clean up created alert
+ const alertsToDelete = await getAlertsByName(alertName);
+ await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id));
+ });
+
it('should create an alert with actions in multiple groups', async () => {
const alertName = generateUniqueKey();
await defineAlwaysFiringAlert(alertName);
@@ -542,5 +603,34 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await deleteConnectorByName('webhook-test');
});
+
+ it('should add filter', async () => {
+ const ruleName = generateUniqueKey();
+ await defineAlwaysFiringAlert(ruleName);
+
+ await testSubjects.click('saveRuleButton');
+ await testSubjects.existOrFail('confirmRuleSaveModal');
+ await testSubjects.click('confirmRuleSaveModal > confirmModalConfirmButton');
+ await testSubjects.missingOrFail('confirmRuleSaveModal');
+
+ const toastTitle = await toasts.getTitleAndDismiss();
+ expect(toastTitle).to.eql(`Created rule "${ruleName}"`);
+
+ await testSubjects.click('triggersActionsAlerts');
+
+ const filter = `{
+ "bool": {
+ "filter": [{ "term": { "kibana.alert.rule.name": "${ruleName}" } }]
+ }
+ }`;
+
+ await filterBar.addDslFilter(filter, true);
+
+ await filterBar.hasFilter('query', filter, true);
+
+ // clean up created alert
+ const alertsToDelete = await getAlertsByName(ruleName);
+ await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id));
+ });
});
};
From 288d41d61ec2389b2e8856da75fd0f3107f9c484 Mon Sep 17 00:00:00 2001
From: Yuliia Naumenko
Date: Sun, 13 Oct 2024 13:39:09 -0700
Subject: [PATCH 02/38] [Connectors][GenAI] Inference Service Kibana connector
(#189027)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
Resolves https://github.com/elastic/kibana/issues/188043
This PR adds new connector which is define integration with Elastic
Inference Endpoint via [Inference
APIs](https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-apis.html)
The lifecycle of the Inference Endpoint are managed by the connector
registered handlers:
- `preSaveHook` -
[create](https://www.elastic.co/guide/en/elasticsearch/reference/current/put-inference-api.html)
new Inference Endpoint in the connector create mode (`isEdit === false`)
and
[delete](https://www.elastic.co/guide/en/elasticsearch/reference/current/delete-inference-api.html)+[create](https://www.elastic.co/guide/en/elasticsearch/reference/current/put-inference-api.html)
in the connector edit mode (`isEdit === true`)
- `postSaveHook` - check if the connector SO was created/updated and if
not removes Inference Endpoint from preSaveHook
- `postDeleteHook` -
[delete](https://www.elastic.co/guide/en/elasticsearch/reference/current/delete-inference-api.html)
Inference Endpoint if connector was deleted.
In the Kibana Stack Management Connectors, its represented with the new
card (Technical preview badge):
To simplify the future integration with AI Assistants, the Connector
consists from the two main UI parts: provider selector and required
provider settings, which will be always displayed
and Additional options, which contains optional provider settings and
Task Type configuration:
subActions corresponds to the different taskTypes Inference API
supports. Each of the task type has its own Inference Perform params.
Currently added:
- completion & completionStream
- rerank
- text_embedding
- sparse_embedding
Follow up work:
1. Collapse/expand Additional options, when the connector flyout/modal
has AI Assistant as a context (path through the extending context
implementation on the connector framework level)
2. Add support for additional params for Completion subAction to be able
to path functions
3. Add support for tokens usage Dashboard, when inference API will
include the used tokens count in the response
4. Add functionality and UX for migration from existing specific AI
connectors to the Inference connector with proper provider and
completion task
5. Integrate Connector with the AI Assistants
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: István Zoltán Szabó
Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com>
Co-authored-by: Steph Milovic
---
.github/CODEOWNERS | 5 +
docs/management/action-types.asciidoc | 4 +
.../action-types/inference.asciidoc | 126 ++
.../images/inference-completion-params.png | Bin 0 -> 115726 bytes
.../connectors/images/inference-connector.png | Bin 0 -> 224091 bytes
docs/management/connectors/index.asciidoc | 1 +
docs/settings/alert-action-settings.asciidoc | 1 +
...connector_types_generativeai_response.yaml | 11 +
.../use_load_action_types/index.tsx | 12 +-
.../common/connector_feature_config.ts | 2 +-
.../components/schemas/connector_types.yaml | 1 +
.../components/schemas/inference_config.yaml | 23 +
.../components/schemas/inference_secrets.yaml | 9 +
.../connector_types.test.ts.snap | 347 ++++++
.../mocks/connector_types.ts | 1 +
.../common/experimental_features.ts | 1 +
.../common/inference/constants.ts | 43 +
.../common/inference/schema.ts | 68 ++
.../common/inference/types.ts | 38 +
.../public/connector_types/index.ts | 4 +
.../inference/additional_options_fields.tsx | 360 ++++++
.../inference/connector.test.tsx | 353 ++++++
.../connector_types/inference/connector.tsx | 445 +++++++
.../connector_types/inference/constants.tsx | 37 +
.../inference/get_task_types.test.ts | 50 +
.../inference/get_task_types.ts | 606 ++++++++++
.../connector_types/inference/helpers.ts | 85 ++
.../inference/hidden_fields.tsx | 88 ++
.../public/connector_types/inference/index.ts | 8 +
.../inference/inference.test.tsx | 142 +++
.../connector_types/inference/inference.tsx | 92 ++
.../connector_types/inference/params.test.tsx | 167 +++
.../connector_types/inference/params.tsx | 238 ++++
.../providers/assets/images/alibaba_cloud.svg | 3 +
.../assets/images/amazon_bedrock.svg | 11 +
.../providers/assets/images/anthropic.svg | 3 +
.../assets/images/azure_ai_studio.svg | 44 +
.../providers/assets/images/azure_open_ai.svg | 9 +
.../providers/assets/images/cohere.svg | 9 +
.../providers/assets/images/elastic.svg | 16 +
.../assets/images/google_ai_studio.svg | 6 +
.../providers/assets/images/hugging_face.svg | 10 +
.../providers/assets/images/ibm_watsonx.svg | 3 +
.../providers/assets/images/mistral.svg | 34 +
.../providers/assets/images/open_ai.svg | 3 +
.../providers/get_providers.test.tsx | 53 +
.../inference/providers/get_providers.ts | 1054 +++++++++++++++++
.../service_provider.test.tsx | 42 +
.../service_provider.tsx | 126 ++
.../providers/selectable/index.test.tsx | 60 +
.../inference/providers/selectable/index.tsx | 136 +++
.../connector_types/inference/translations.ts | 107 ++
.../public/connector_types/inference/types.ts | 41 +
.../connector_configuration_field.tsx | 360 ++++++
.../connector_configuration_form_items.tsx | 136 +++
.../connector_configuration_utils.ts | 55 +
.../lib/dynamic_config/types.ts | 71 ++
.../server/connector_types/index.ts | 4 +
.../connector_types/inference/index.test.ts | 85 ++
.../server/connector_types/inference/index.ts | 182 +++
.../inference/inference.test.ts | 310 +++++
.../connector_types/inference/inference.ts | 232 ++++
.../lib/gen_ai/create_gen_ai_dashboard.ts | 2 +-
.../lib/gen_ai/gen_ai_dashboard.ts | 12 +-
.../lib/unflatten_object.test.ts | 45 +
.../connector_types/lib/unflatten_object.ts | 16 +
.../stack_connectors/server/plugin.test.ts | 2 +-
x-pack/plugins/stack_connectors/tsconfig.json | 2 +
.../task_cost_check.test.ts.snap | 4 +
.../create_connector_flyout/header.tsx | 18 +-
.../encrypted_fields_callout.test.tsx | 16 +-
.../encrypted_fields_callout.tsx | 4 +-
.../server/inference_simulation.ts | 53 +
.../actions/connector_types/inference.ts | 518 ++++++++
.../check_registered_connector_types.ts | 1 +
.../check_registered_task_types.ts | 1 +
76 files changed, 7251 insertions(+), 16 deletions(-)
create mode 100644 docs/management/connectors/action-types/inference.asciidoc
create mode 100644 docs/management/connectors/images/inference-completion-params.png
create mode 100644 docs/management/connectors/images/inference-connector.png
create mode 100644 x-pack/plugins/actions/docs/openapi/components/schemas/inference_config.yaml
create mode 100644 x-pack/plugins/actions/docs/openapi/components/schemas/inference_secrets.yaml
create mode 100644 x-pack/plugins/stack_connectors/common/inference/constants.ts
create mode 100644 x-pack/plugins/stack_connectors/common/inference/schema.ts
create mode 100644 x-pack/plugins/stack_connectors/common/inference/types.ts
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/additional_options_fields.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/connector.test.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/connector.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/constants.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/get_task_types.test.ts
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/get_task_types.ts
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/helpers.ts
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/hidden_fields.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/index.ts
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/inference.test.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/inference.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/params.test.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/params.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/assets/images/alibaba_cloud.svg
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/assets/images/amazon_bedrock.svg
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/assets/images/anthropic.svg
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/assets/images/azure_ai_studio.svg
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/assets/images/azure_open_ai.svg
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/assets/images/cohere.svg
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/assets/images/elastic.svg
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/assets/images/google_ai_studio.svg
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/assets/images/hugging_face.svg
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/assets/images/ibm_watsonx.svg
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/assets/images/mistral.svg
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/assets/images/open_ai.svg
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/get_providers.test.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/get_providers.ts
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/render_service_provider/service_provider.test.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/render_service_provider/service_provider.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/selectable/index.test.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/providers/selectable/index.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/translations.ts
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/inference/types.ts
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_field.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_form_items.tsx
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/connector_configuration_utils.ts
create mode 100644 x-pack/plugins/stack_connectors/public/connector_types/lib/dynamic_config/types.ts
create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/inference/index.test.ts
create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/inference/index.ts
create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/inference/inference.test.ts
create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/inference/inference.ts
create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/lib/unflatten_object.test.ts
create mode 100644 x-pack/plugins/stack_connectors/server/connector_types/lib/unflatten_object.ts
create mode 100644 x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/inference_simulation.ts
create mode 100644 x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/inference.ts
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index fdd0d98d8d53b4..a8cc39b7b072b9 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1681,6 +1681,11 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/
/x-pack/plugins/stack_connectors/server/connector_types/gemini @elastic/security-generative-ai @elastic/obs-ai-assistant @elastic/appex-ai-infra
/x-pack/plugins/stack_connectors/common/gemini @elastic/security-generative-ai @elastic/obs-ai-assistant @elastic/appex-ai-infra
+# Inference API
+/x-pack/plugins/stack_connectors/public/connector_types/inference @elastic/appex-ai-infra @elastic/security-generative-ai @elastic/obs-ai-assistant
+/x-pack/plugins/stack_connectors/server/connector_types/inference @elastic/appex-ai-infra @elastic/security-generative-ai @elastic/obs-ai-assistant
+/x-pack/plugins/stack_connectors/common/inference @elastic/appex-ai-infra @elastic/security-generative-ai @elastic/obs-ai-assistant
+
## Defend Workflows owner connectors
/x-pack/plugins/stack_connectors/public/connector_types/sentinelone @elastic/security-defend-workflows
/x-pack/plugins/stack_connectors/server/connector_types/sentinelone @elastic/security-defend-workflows
diff --git a/docs/management/action-types.asciidoc b/docs/management/action-types.asciidoc
index 30bf4f791e5d81..361892e430afde 100644
--- a/docs/management/action-types.asciidoc
+++ b/docs/management/action-types.asciidoc
@@ -28,6 +28,10 @@ a| <>
| Send a request to {gemini}.
+a| <>
+
+| Send a request to {inference}.
+
a| <>
| Send email from your server.
diff --git a/docs/management/connectors/action-types/inference.asciidoc b/docs/management/connectors/action-types/inference.asciidoc
new file mode 100644
index 00000000000000..8c7f2840f9c5c4
--- /dev/null
+++ b/docs/management/connectors/action-types/inference.asciidoc
@@ -0,0 +1,126 @@
+[[inference-action-type]]
+== {infer-cap} connector and action
+++++
+{inference}
+++++
+:frontmatter-description: Add a connector that can send requests to {inference}.
+:frontmatter-tags-products: [kibana]
+:frontmatter-tags-content-type: [how-to]
+:frontmatter-tags-user-goals: [configure]
+
+
+The {infer} connector uses the {es} client to send requests to an {infer} service. The connector uses the <> to send the request.
+
+[float]
+[[define-inference-ui]]
+=== Create connectors in {kib}
+
+You can create connectors in *{stack-manage-app} > {connectors-ui}*. For example:
+
+[role="screenshot"]
+image::management/connectors/images/inference-connector.png[{inference} connector]
+// NOTE: This is an autogenerated screenshot. Do not edit it directly.
+
+[float]
+[[inference-connector-configuration]]
+==== Connector configuration
+
+{infer-cap} connectors have the following configuration properties:
+
+Name:: The name of the connector.
+Service:: The supported {infer} service provider.
+Task type:: The {infer} task type, it depends on the selected service.
+Inference ID:: The unique identifier of the {infer} endpoint.
+Provider configuration:: Settings for service configuration.
+Provider secrets:: Configuration for authentication.
+Task type configuration:: Settings for task type configuration.
+
+[float]
+[[inference-action-configuration]]
+=== Test connectors
+
+You can test connectors using the <> or
+while creating or editing the connector in {kib}. For example:
+
+[role="screenshot"]
+image::management/connectors/images/inference-completion-params.png[{infer} params test]
+// NOTE: This is an autogenerated screenshot. Do not edit it directly.
+[float]
+[[inference-connector-actions]]
+=== {infer-cap} connector actions
+
+The {infer} actions have the following configuration properties. Properties depend on the selected task type.
+
+[float]
+[[inference-connector-perform-completion]]
+==== Completion
+
+The following example performs a completion task on the example question.
+Input::
+The text on which you want to perform the {infer} task. For example:
++
+[source,text]
+--
+{
+ input: 'What is Elastic?'
+}
+--
+
+[float]
+[[inference-connector-perform-text-embedding]]
+==== Text embedding
+
+The following example performs a text embedding task.
+Input::
+The text on which you want to perform the {infer} task. For example:
++
+[source,text]
+--
+{
+ input: 'The sky above the port was the color of television tuned to a dead channel.',
+ task_settings: {
+ input_type: 'ingest'
+ }
+}
+--
+Input type::
+An optional string that overwrites the connector's default model.
+
+[float]
+[[inference-connector-perform-rerank]]
+==== Reranking
+
+The following example performs a reranking task on the example input.
+Input::
+The text on which you want to perform the {infer} task. Should be a string array. For example:
++
+[source,text]
+--
+{
+ input: ['luke', 'like', 'leia', 'chewy', 'r2d2', 'star', 'wars'],
+ query: 'star wars main character'
+}
+--
+Query::
+The search query text.
+
+[float]
+[[inference-connector-perform-sparse-embedding]]
+==== Sparse embedding
+
+The following example performs a sparse embedding task on the example sentence.
+Input::
+The text on which you want to perform the {infer} task. For example:
++
+[source,text]
+--
+{
+ input: 'The sky above the port was the color of television tuned to a dead channel.'
+}
+--
+
+[float]
+[[inference-connector-networking-configuration]]
+=== Connector networking configuration
+
+Use the <> to customize connector networking configurations, such as proxies, certificates, or TLS settings. You can apply these settings to all your connectors or use `xpack.actions.customHostSettings` to set per-host configurations.
diff --git a/docs/management/connectors/images/inference-completion-params.png b/docs/management/connectors/images/inference-completion-params.png
new file mode 100644
index 0000000000000000000000000000000000000000..686ee9771c2f56a80fa2175aeecc7eb4ef82f60f
GIT binary patch
literal 115726
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