[ResponseOps][Rules] Fix KQL wildcards in alerts filtering in actions and MW (#183901)

## Summary

This PR 

1. Show KQL error messages to the UI
2. Respect the `query:allowLeadingWildcards` advance setting in the MW

## Testing

Verify that the bug except the one about DSL filtering described in
https://github.com/elastic/kibana/issues/168600 is fixed. Also, test the
following scenarios.

### Actions

**Error**:
1. Go to Stack -> Advanced setting and disable
`query:allowLeadingWildcards`
2. Create a rule with an action and make the action conditional by
toggling the "If alert matches query"
3. Add a KQL like `kibana.alert.instance.id : *development`. The leading
`*` is important
4. Save the rule. You should see a toaster error with a message about
`query:allowLeadingWildcards`

**Happy path**:
1. Go to Stack -> Advanced setting and make sure
`query:allowLeadingWildcards` is enabled
2. Create a rule with an action and make the action conditional by
toggling the "If alert matches query"
3. Add a KQL like `kibana.alert.instance.id : *development`. The leading
`*` is important
4. Save the rule. You should not see any errors.

### Maintenance Windows

**Error**:
1. Go to Stack -> Advanced setting and disable
`query:allowLeadingWildcards`
2. Go to Stack -> Maintenance Windows -> Create window
3. Toggle "Filter alerts" and add a KQL like `kibana.alert.instance.id :
*development`. The leading `*` is important
4. Create the MW. You should see a toaster error with a message about
`query:allowLeadingWildcards`

**Happy path**:
1. Go to Stack -> Advanced setting and make sure
`query:allowLeadingWildcards` is enabled
2. Go to Stack -> Maintenance Windows -> Create window
3. Toggle "Filter alerts" and add a KQL like `kibana.alert.instance.id :
*development`. The leading `*` is important
4. Create the MW. You should not see any errors.

Fixes: https://github.com/elastic/kibana/issues/168600

### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

## Release notes
Show errors about invalid KQL in conditional actions and respect the
`query:allowLeadingWildcards` advanced setting in maintenance windows
This commit is contained in:
Christos Nasikas 2024-05-29 13:32:12 +03:00 committed by GitHub
parent 690690ea21
commit b9e47025fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 218 additions and 41 deletions

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Logger, SavedObjectsClientContract } from '@kbn/core/server';
import type { IUiSettingsClient, Logger, SavedObjectsClientContract } from '@kbn/core/server';
import { FilterStateStore } from '@kbn/es-query';
import { RRuleParams } from './rrule_type';
@ -76,6 +76,7 @@ export type MaintenanceWindowCreateBody = Omit<
>;
export interface MaintenanceWindowClientContext {
readonly uiSettings: IUiSettingsClient;
getModificationMetadata: () => Promise<MaintenanceWindowModificationMetadata>;
savedObjectsClient: SavedObjectsClientContract;
logger: Logger;

View file

@ -79,7 +79,25 @@ describe('useCreateMaintenanceWindow', () => {
});
await waitFor(() =>
expect(mockAddDanger).toBeCalledWith('Failed to create maintenance window.')
expect(mockAddDanger).toBeCalledWith('Failed to create maintenance window')
);
});
it('should show 400 error messages', async () => {
createMaintenanceWindow.mockRejectedValue({
body: { statusCode: 400, message: 'Bad request' },
});
const { result } = renderHook(() => useCreateMaintenanceWindow(), {
wrapper: appMockRenderer.AppWrapper,
});
act(() => {
result.current.mutate(maintenanceWindow);
});
await waitFor(() =>
expect(mockAddDanger).toBeCalledWith('Failed to create maintenance window: Bad request')
);
});
});

View file

@ -13,6 +13,19 @@ import type { KibanaServerError } from '@kbn/kibana-utils-plugin/public';
import { useKibana } from '../utils/kibana_react';
import { createMaintenanceWindow, CreateParams } from '../services/maintenance_windows_api/create';
const onErrorWithMessage = (message: string) =>
i18n.translate('xpack.alerting.maintenanceWindowsCreateFailureWithMessage', {
defaultMessage: 'Failed to create maintenance window: {message}',
values: { message },
});
const onErrorWithoutMessage = i18n.translate(
'xpack.alerting.maintenanceWindowsCreateFailureWithoutMessage',
{
defaultMessage: 'Failed to create maintenance window',
}
);
interface UseCreateMaintenanceWindowProps {
onError?: (error: IHttpFetchError<KibanaServerError>) => void;
}
@ -41,10 +54,11 @@ export function useCreateMaintenanceWindow(props?: UseCreateMaintenanceWindowPro
);
},
onError: (error: IHttpFetchError<KibanaServerError>) => {
const getDefaultErrorMessage = (message?: string): string =>
!message ? onErrorWithoutMessage : onErrorWithMessage(message);
toasts.addDanger(
i18n.translate('xpack.alerting.maintenanceWindowsCreateFailure', {
defaultMessage: 'Failed to create maintenance window.',
})
getDefaultErrorMessage(error.body?.statusCode === 400 ? error.body?.message : '')
);
onError?.(error);
},

View file

@ -8,7 +8,11 @@
import moment from 'moment-timezone';
import { Frequency } from '@kbn/rrule';
import { archiveMaintenanceWindow } from './archive_maintenance_window';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
import {
savedObjectsClientMock,
loggingSystemMock,
uiSettingsServiceMock,
} from '@kbn/core/server/mocks';
import { SavedObjectsUpdateResponse, SavedObject } from '@kbn/core/server';
import {
MaintenanceWindowClientContext,
@ -18,6 +22,7 @@ import { getMockMaintenanceWindow } from '../../../../data/maintenance_window/te
import type { MaintenanceWindow } from '../../types';
const savedObjectsClient = savedObjectsClientMock.create();
const uiSettings = uiSettingsServiceMock.createClient();
const firstTimestamp = '2023-02-26T00:00:00.000Z';
const secondTimestamp = '2023-03-26T00:00:00.000Z';
@ -33,6 +38,7 @@ const mockContext: jest.Mocked<MaintenanceWindowClientContext> = {
logger: loggingSystemMock.create().get(),
getModificationMetadata: jest.fn(),
savedObjectsClient,
uiSettings,
};
describe('MaintenanceWindowClient - archive', () => {

View file

@ -6,7 +6,11 @@
*/
import { bulkGetMaintenanceWindows } from './bulk_get_maintenance_windows';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
import {
savedObjectsClientMock,
loggingSystemMock,
uiSettingsServiceMock,
} from '@kbn/core/server/mocks';
import { SavedObject } from '@kbn/core/server';
import {
MaintenanceWindowClientContext,
@ -15,11 +19,13 @@ import {
import { getMockMaintenanceWindow } from '../../../../data/maintenance_window/test_helpers';
const savedObjectsClient = savedObjectsClientMock.create();
const uiSettings = uiSettingsServiceMock.createClient();
const mockContext: jest.Mocked<MaintenanceWindowClientContext> = {
logger: loggingSystemMock.create().get(),
getModificationMetadata: jest.fn(),
savedObjectsClient,
uiSettings,
};
describe('MaintenanceWindowClient - get', () => {

View file

@ -8,7 +8,11 @@
import moment from 'moment-timezone';
import { createMaintenanceWindow } from './create_maintenance_window';
import { CreateMaintenanceWindowParams } from './types';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
import {
savedObjectsClientMock,
loggingSystemMock,
uiSettingsServiceMock,
} from '@kbn/core/server/mocks';
import { SavedObject } from '@kbn/core/server';
import {
MaintenanceWindowClientContext,
@ -19,6 +23,7 @@ import type { MaintenanceWindow } from '../../types';
import { FilterStateStore } from '@kbn/es-query';
const savedObjectsClient = savedObjectsClientMock.create();
const uiSettings = uiSettingsServiceMock.createClient();
const updatedMetadata = {
createdAt: '2023-03-26T00:00:00.000Z',
@ -31,6 +36,7 @@ const mockContext: jest.Mocked<MaintenanceWindowClientContext> = {
logger: loggingSystemMock.create().get(),
getModificationMetadata: jest.fn(),
savedObjectsClient,
uiSettings,
};
describe('MaintenanceWindowClient - create', () => {
@ -223,6 +229,21 @@ describe('MaintenanceWindowClient - create', () => {
).toMatchInlineSnapshot(
`"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"_id\\":\\"'1234'\\"}}],\\"minimum_should_match\\":1}},{\\"match_phrase\\":{\\"kibana.alert.action_group\\":\\"test\\"}}],\\"should\\":[],\\"must_not\\":[]}}"`
);
expect(uiSettings.get).toHaveBeenCalledTimes(3);
expect(uiSettings.get.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"query:allowLeadingWildcards",
],
Array [
"query:queryString:options",
],
Array [
"courier:ignoreFilterIfFieldNotInIndex",
],
]
`);
});
it('should throw if trying to create a maintenance window with invalid scoped query', async () => {

View file

@ -9,6 +9,7 @@ import moment from 'moment';
import Boom from '@hapi/boom';
import { SavedObjectsUtils } from '@kbn/core/server';
import { buildEsQuery, Filter } from '@kbn/es-query';
import { getEsQueryConfig } from '../../../../lib/get_es_query_config';
import { generateMaintenanceWindowEvents } from '../../lib/generate_maintenance_window_events';
import type { MaintenanceWindowClientContext } from '../../../../../common';
import { getScopedQueryErrorMessage } from '../../../../../common';
@ -26,8 +27,9 @@ export async function createMaintenanceWindow(
params: CreateMaintenanceWindowParams
): Promise<MaintenanceWindow> {
const { data } = params;
const { savedObjectsClient, getModificationMetadata, logger } = context;
const { savedObjectsClient, getModificationMetadata, logger, uiSettings } = context;
const { title, duration, rRule, categoryIds, scopedQuery } = data;
const esQueryConfig = await getEsQueryConfig(uiSettings);
try {
createMaintenanceWindowParamsSchema.validate(params);
@ -36,15 +38,18 @@ export async function createMaintenanceWindow(
}
let scopedQueryWithGeneratedValue = scopedQuery;
try {
if (scopedQuery) {
const dsl = JSON.stringify(
buildEsQuery(
undefined,
[{ query: scopedQuery.kql, language: 'kuery' }],
scopedQuery.filters as Filter[]
scopedQuery.filters as Filter[],
esQueryConfig
)
);
scopedQueryWithGeneratedValue = {
...scopedQuery,
dsl,

View file

@ -6,18 +6,24 @@
*/
import { deleteMaintenanceWindow } from './delete_maintenance_window';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
import {
savedObjectsClientMock,
loggingSystemMock,
uiSettingsServiceMock,
} from '@kbn/core/server/mocks';
import {
MaintenanceWindowClientContext,
MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE,
} from '../../../../../common';
const savedObjectsClient = savedObjectsClientMock.create();
const uiSettings = uiSettingsServiceMock.createClient();
const mockContext: jest.Mocked<MaintenanceWindowClientContext> = {
logger: loggingSystemMock.create().get(),
getModificationMetadata: jest.fn(),
savedObjectsClient,
uiSettings,
};
describe('MaintenanceWindowClient - delete', () => {

View file

@ -6,7 +6,11 @@
*/
import { findMaintenanceWindows } from './find_maintenance_windows';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
import {
savedObjectsClientMock,
loggingSystemMock,
uiSettingsServiceMock,
} from '@kbn/core/server/mocks';
import { SavedObjectsFindResponse } from '@kbn/core/server';
import {
MaintenanceWindowClientContext,
@ -15,11 +19,13 @@ import {
import { getMockMaintenanceWindow } from '../../../../data/maintenance_window/test_helpers';
const savedObjectsClient = savedObjectsClientMock.create();
const uiSettings = uiSettingsServiceMock.createClient();
const mockContext: jest.Mocked<MaintenanceWindowClientContext> = {
logger: loggingSystemMock.create().get(),
getModificationMetadata: jest.fn(),
savedObjectsClient,
uiSettings,
};
describe('MaintenanceWindowClient - find', () => {

View file

@ -8,7 +8,11 @@
import moment from 'moment-timezone';
import { Frequency } from '@kbn/rrule';
import { finishMaintenanceWindow } from './finish_maintenance_window';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
import {
savedObjectsClientMock,
loggingSystemMock,
uiSettingsServiceMock,
} from '@kbn/core/server/mocks';
import { SavedObjectsUpdateResponse, SavedObject } from '@kbn/core/server';
import {
MaintenanceWindowClientContext,
@ -18,6 +22,7 @@ import { getMockMaintenanceWindow } from '../../../../data/maintenance_window/te
import type { MaintenanceWindow } from '../../types';
const savedObjectsClient = savedObjectsClientMock.create();
const uiSettings = uiSettingsServiceMock.createClient();
const firstTimestamp = '2023-02-26T00:00:00.000Z';
@ -32,6 +37,7 @@ const mockContext: jest.Mocked<MaintenanceWindowClientContext> = {
logger: loggingSystemMock.create().get(),
getModificationMetadata: jest.fn(),
savedObjectsClient,
uiSettings,
};
describe('MaintenanceWindowClient - finish', () => {

View file

@ -6,7 +6,11 @@
*/
import { getMaintenanceWindow } from './get_maintenance_window';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
import {
savedObjectsClientMock,
loggingSystemMock,
uiSettingsServiceMock,
} from '@kbn/core/server/mocks';
import { SavedObject } from '@kbn/core/server';
import {
MaintenanceWindowClientContext,
@ -15,11 +19,13 @@ import {
import { getMockMaintenanceWindow } from '../../../../data/maintenance_window/test_helpers';
const savedObjectsClient = savedObjectsClientMock.create();
const uiSettings = uiSettingsServiceMock.createClient();
const mockContext: jest.Mocked<MaintenanceWindowClientContext> = {
logger: loggingSystemMock.create().get(),
getModificationMetadata: jest.fn(),
savedObjectsClient,
uiSettings,
};
describe('MaintenanceWindowClient - get', () => {

View file

@ -7,7 +7,11 @@
import { getActiveMaintenanceWindows } from './get_active_maintenance_windows';
import { toElasticsearchQuery } from '@kbn/es-query';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
import {
savedObjectsClientMock,
loggingSystemMock,
uiSettingsServiceMock,
} from '@kbn/core/server/mocks';
import { SavedObjectsFindResponse } from '@kbn/core/server';
import {
MaintenanceWindowClientContext,
@ -16,11 +20,13 @@ import {
import { getMockMaintenanceWindow } from '../../../../data/maintenance_window/test_helpers';
const savedObjectsClient = savedObjectsClientMock.create();
const uiSettings = uiSettingsServiceMock.createClient();
const mockContext: jest.Mocked<MaintenanceWindowClientContext> = {
logger: loggingSystemMock.create().get(),
getModificationMetadata: jest.fn(),
savedObjectsClient,
uiSettings,
};
describe('MaintenanceWindowClient - getActiveMaintenanceWindows', () => {

View file

@ -9,7 +9,11 @@ import moment from 'moment-timezone';
import { Frequency } from '@kbn/rrule';
import { updateMaintenanceWindow } from './update_maintenance_window';
import { UpdateMaintenanceWindowParams } from './types';
import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
import {
savedObjectsClientMock,
loggingSystemMock,
uiSettingsServiceMock,
} from '@kbn/core/server/mocks';
import { SavedObject } from '@kbn/core/server';
import {
MaintenanceWindowClientContext,
@ -20,6 +24,7 @@ import type { MaintenanceWindow } from '../../types';
import { FilterStateStore } from '@kbn/es-query';
const savedObjectsClient = savedObjectsClientMock.create();
const uiSettings = uiSettingsServiceMock.createClient();
const firstTimestamp = '2023-02-26T00:00:00.000Z';
const secondTimestamp = '2023-03-26T00:00:00.000Z';
@ -47,6 +52,7 @@ const mockContext: jest.Mocked<MaintenanceWindowClientContext> = {
logger: loggingSystemMock.create().get(),
getModificationMetadata: jest.fn(),
savedObjectsClient,
uiSettings,
};
describe('MaintenanceWindowClient - update', () => {

View file

@ -0,0 +1,22 @@
/*
* 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 { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks';
import { getEsQueryConfig } from './get_es_query_config';
describe('getEsQueryConfig', () => {
const uiSettingsClient = uiSettingsServiceMock.createClient();
it('should get the es query config correctly', async () => {
const settings = await getEsQueryConfig(uiSettingsClient);
expect(settings).toEqual({
allowLeadingWildcards: false,
ignoreFilterIfFieldNotInIndex: false,
queryStringOptions: false,
});
});
});

View file

@ -0,0 +1,23 @@
/*
* 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 { IUiSettingsClient } from '@kbn/core/server';
import { UI_SETTINGS } from '@kbn/data-plugin/server';
export async function getEsQueryConfig(uiSettings: IUiSettingsClient) {
const allowLeadingWildcards = await uiSettings.get(UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS);
const queryStringOptions = await uiSettings.get(UI_SETTINGS.QUERY_STRING_OPTIONS);
const ignoreFilterIfFieldNotInIndex = await uiSettings.get(
UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX
);
return {
allowLeadingWildcards,
queryStringOptions,
ignoreFilterIfFieldNotInIndex,
};
}

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Logger, SavedObjectsClientContract } from '@kbn/core/server';
import { IUiSettingsClient, Logger, SavedObjectsClientContract } from '@kbn/core/server';
import { createMaintenanceWindow } from '../application/maintenance_window/methods/create/create_maintenance_window';
import type { CreateMaintenanceWindowParams } from '../application/maintenance_window/methods/create/types';
@ -33,6 +33,7 @@ import {
import type { MaintenanceWindow } from '../application/maintenance_window/types';
export interface MaintenanceWindowClientConstructorOptions {
readonly uiSettings: IUiSettingsClient;
readonly logger: Logger;
readonly savedObjectsClient: SavedObjectsClientContract;
readonly getUserName: () => Promise<string | null>;
@ -52,6 +53,7 @@ export class MaintenanceWindowClient {
logger: this.logger,
savedObjectsClient: this.savedObjectsClient,
getModificationMetadata: this.getModificationMetadata.bind(this),
uiSettings: options.uiSettings,
};
}

View file

@ -13,6 +13,7 @@ import {
savedObjectsClientMock,
savedObjectsServiceMock,
loggingSystemMock,
uiSettingsServiceMock,
} from '@kbn/core/server/mocks';
import { AuthenticatedUser } from '@kbn/security-plugin/common';
import { securityMock } from '@kbn/security-plugin/server/mocks';
@ -23,12 +24,13 @@ jest.mock('./maintenance_window_client');
const savedObjectsClient = savedObjectsClientMock.create();
const savedObjectsService = savedObjectsServiceMock.createInternalStartContract();
const securityPluginStart = securityMock.createStart();
const uiSettings = uiSettingsServiceMock.createStartContract();
const maintenanceWindowClientFactoryParams: jest.Mocked<MaintenanceWindowClientFactoryOpts> = {
logger: loggingSystemMock.create().get(),
savedObjectsService,
uiSettings,
};
beforeEach(() => {

View file

@ -10,6 +10,7 @@ import {
Logger,
SavedObjectsServiceStart,
SECURITY_EXTENSION_ID,
UiSettingsServiceStart,
} from '@kbn/core/server';
import { SecurityPluginStart } from '@kbn/security-plugin/server';
import { MaintenanceWindowClient } from './maintenance_window_client';
@ -19,6 +20,7 @@ export interface MaintenanceWindowClientFactoryOpts {
logger: Logger;
savedObjectsService: SavedObjectsServiceStart;
securityPluginStart?: SecurityPluginStart;
uiSettings: UiSettingsServiceStart;
}
export class MaintenanceWindowClientFactory {
@ -26,6 +28,7 @@ export class MaintenanceWindowClientFactory {
private logger!: Logger;
private savedObjectsService!: SavedObjectsServiceStart;
private securityPluginStart?: SecurityPluginStart;
private uiSettings!: UiSettingsServiceStart;
public initialize(options: MaintenanceWindowClientFactoryOpts) {
if (this.isInitialized) {
@ -35,6 +38,7 @@ export class MaintenanceWindowClientFactory {
this.logger = options.logger;
this.savedObjectsService = options.savedObjectsService;
this.securityPluginStart = options.securityPluginStart;
this.uiSettings = options.uiSettings;
}
private createMaintenanceWindowClient(request: KibanaRequest, withAuth: boolean) {
@ -44,9 +48,12 @@ export class MaintenanceWindowClientFactory {
...(withAuth ? {} : { excludedExtensions: [SECURITY_EXTENSION_ID] }),
});
const uiSettingClient = this.uiSettings.asScopedToClient(savedObjectsClient);
return new MaintenanceWindowClient({
logger: this.logger,
savedObjectsClient,
uiSettings: uiSettingClient,
async getUserName() {
if (!securityPluginStart || !request) {
return null;

View file

@ -556,6 +556,7 @@ export class AlertingPlugin {
logger: this.logger,
savedObjectsService: core.savedObjects,
securityPluginStart: plugins.security,
uiSettings: core.uiSettings,
});
const getRulesClientWithRequest = (request: KibanaRequest) => {

View file

@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`addGeneratedActionValues() throws error if KQL is not valid: "Error creating DSL query: invalid KQL" 1`] = `"Error creating DSL query: invalid KQL"`;

View file

@ -32,14 +32,16 @@ describe('addGeneratedActionValues()', () => {
const taskManager = taskManagerMock.createStart();
const ruleTypeRegistry = ruleTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
const kibanaVersion = 'v7.10.0';
const logger = loggingSystemMock.create().get();
const uiSettings = uiSettingsServiceMock.createStartContract();
const uiSettingsClient = uiSettingsServiceMock.createClient();
uiSettings.asScopedToClient.mockReturnValue(uiSettingsClient);
const rulesClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
@ -64,7 +66,7 @@ describe('addGeneratedActionValues()', () => {
getAlertIndicesAlias: jest.fn(),
alertsService: null,
backfillClient: backfillClientMock.create(),
uiSettings: uiSettingsServiceMock.createStartContract(),
uiSettings,
connectorAdapterRegistry: new ConnectorAdapterRegistry(),
isSystemAction: jest.fn(),
};
@ -103,6 +105,10 @@ describe('addGeneratedActionValues()', () => {
params: {},
};
beforeEach(() => {
jest.clearAllMocks();
});
test('adds uuid', async () => {
const actionWithGeneratedValues = await addGeneratedActionValues(
[mockAction],
@ -145,6 +151,21 @@ describe('addGeneratedActionValues()', () => {
params: {},
uuid: '111-222',
});
expect(uiSettingsClient.get).toHaveBeenCalledTimes(3);
expect(uiSettingsClient.get.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"query:allowLeadingWildcards",
],
Array [
"query:queryString:options",
],
Array [
"courier:ignoreFilterIfFieldNotInIndex",
],
]
`);
});
test('throws error if KQL is not valid', async () => {
@ -156,6 +177,7 @@ describe('addGeneratedActionValues()', () => {
alertsFilter: { query: { kql: 'foo:bar:1', filters: [] } },
},
],
[mockSystemAction],
{
...rulesClientParams,
@ -163,6 +185,12 @@ describe('addGeneratedActionValues()', () => {
minimumScheduleIntervalInMs: 0,
}
)
).rejects.toThrowErrorMatchingSnapshot('"Error creating DSL query: invalid KQL"');
).rejects.toThrowErrorMatchingInlineSnapshot(
`
"Invalid KQL: Expected AND, OR, end of input but \\":\\" found.
foo:bar:1
-------^"
`
);
});
});

View file

@ -8,7 +8,6 @@
import { v4 } from 'uuid';
import { buildEsQuery, Filter } from '@kbn/es-query';
import Boom from '@hapi/boom';
import { UI_SETTINGS } from '@kbn/data-plugin/common';
import {
NormalizedAlertAction,
NormalizedAlertDefaultActionWithGeneratedValues,
@ -16,6 +15,7 @@ import {
NormalizedSystemAction,
RulesClientContext,
} from '..';
import { getEsQueryConfig } from '../../lib/get_es_query_config';
export async function addGeneratedActionValues(
actions: NormalizedAlertAction[] = [],
@ -26,24 +26,15 @@ export async function addGeneratedActionValues(
systemActions: NormalizedAlertSystemActionWithGeneratedValues[];
}> {
const uiSettingClient = context.uiSettings.asScopedToClient(context.unsecuredSavedObjectsClient);
const [allowLeadingWildcards, queryStringOptions, ignoreFilterIfFieldNotInIndex] =
await Promise.all([
uiSettingClient.get(UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS),
uiSettingClient.get(UI_SETTINGS.QUERY_STRING_OPTIONS),
uiSettingClient.get(UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX),
]);
const esQueryConfig = {
allowLeadingWildcards,
queryStringOptions,
ignoreFilterIfFieldNotInIndex,
};
const esQueryConfig = await getEsQueryConfig(uiSettingClient);
const generateDSL = (kql: string, filters: Filter[]): string => {
try {
return JSON.stringify(
buildEsQuery(undefined, [{ query: kql, language: 'kuery' }], filters, esQueryConfig)
);
} catch (e) {
throw Boom.badRequest(`Error creating DSL query: invalid KQL`);
throw Boom.badRequest(`Invalid KQL: ${e.message}`);
}
};

View file

@ -8567,7 +8567,6 @@
"xpack.alerting.maintenanceWindows.upcoming": "À venir",
"xpack.alerting.maintenanceWindowsArchiveFailure": "Impossible d'archiver la fenêtre de maintenance.",
"xpack.alerting.maintenanceWindowsArchiveSuccess": "Fenêtre de maintenance archivée \"{title}\"",
"xpack.alerting.maintenanceWindowsCreateFailure": "Impossible de créer la fenêtre de maintenance.",
"xpack.alerting.maintenanceWindowsCreateSuccess": "Fenêtre de maintenance créée \"{title}\"",
"xpack.alerting.maintenanceWindowsFinishedAndArchiveFailure": "Impossible d'annuler et d'archiver la fenêtre de maintenance.",
"xpack.alerting.maintenanceWindowsFinishedAndArchiveSuccess": "Fenêtre de maintenance en cours d'exécution annulée et archivée \"{title}\"",

View file

@ -8554,7 +8554,6 @@
"xpack.alerting.maintenanceWindows.upcoming": "予定",
"xpack.alerting.maintenanceWindowsArchiveFailure": "保守時間枠をアーカイブできませんでした。",
"xpack.alerting.maintenanceWindowsArchiveSuccess": "アーカイブされた保守時間枠'{title}'",
"xpack.alerting.maintenanceWindowsCreateFailure": "保守時間枠を作成できませんでした。",
"xpack.alerting.maintenanceWindowsCreateSuccess": "作成された保守時間枠'{title}'",
"xpack.alerting.maintenanceWindowsFinishedAndArchiveFailure": "保守時間枠をキャンセルしてアーカイブできませんでした。",
"xpack.alerting.maintenanceWindowsFinishedAndArchiveSuccess": "キャンセルされ、アーカイブされた実行中の保守時間枠'{title}'",

View file

@ -8571,7 +8571,6 @@
"xpack.alerting.maintenanceWindows.upcoming": "即将发生",
"xpack.alerting.maintenanceWindowsArchiveFailure": "无法归档维护窗口。",
"xpack.alerting.maintenanceWindowsArchiveSuccess": "已归档维护窗口“{title}”",
"xpack.alerting.maintenanceWindowsCreateFailure": "无法创建维护窗口。",
"xpack.alerting.maintenanceWindowsCreateSuccess": "已创建维护窗口“{title}”",
"xpack.alerting.maintenanceWindowsFinishedAndArchiveFailure": "无法取消并归档维护窗口。",
"xpack.alerting.maintenanceWindowsFinishedAndArchiveSuccess": "已取消并归档正在运行的维护窗口“{title}”",