Skip to content

Commit d37b516

Browse files
[Alerting v2] Refactor rules list search to use SO client (elastic#265303)
## Summary - Replaces hand-built KQL search fragments with the SO client's native `search`/`searchFields` parameters - Fixes search failures with special characters like consecutive hyphens (`a--b`) that caused KQL parse errors - Adds `search` as a separate field in the bulk operations API, decoupling free-text search from structural KQL filters - Removes `buildRuleSearchQuery`, `buildApiRulesListCombinedFilter`, `buildFindRulesSearch`, and associated test files Closes elastic#261450 Closes elastic/rna-program#415 ## Limitations Search currently matches `metadata.name` and `metadata.description` only. `metadata.tags` and `grouping.fields` are keyword-mapped and incompatible with `simple_query_string` phrase-prefix queries. ## Test plan - [ ] Search by partial rule name returns matching rules - [ ] Multi-word search applies AND logic across terms - [ ] Special characters (e.g. `a--b`) do not cause 400 errors - [ ] Search combined with facet filters works correctly - [ ] Bulk operations (enable/disable/delete) with select-all + search scope to search results - [ ] Bulk operations with select-all + exclusions work correctly --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent c0c368e commit d37b516

21 files changed

Lines changed: 317 additions & 349 deletions

File tree

x-pack/platform/packages/shared/response-ops/alerting-v2-schemas/src/bulk_operation_schema.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ import { BULK_FILTER_MAX_RULES } from './constants';
1212
/**
1313
* Schema for bulk operation request bodies.
1414
*
15-
* Enforces that exactly one of `ids` or `filter` must be provided:
16-
* - `ids`: An explicit list of rule IDs to operate on (1–100).
17-
* - `filter`: A KQL filter string to match rules.
15+
* At least one targeting param must be provided:
16+
* - `ids` — explicit list (cannot be combined with filter/search/match_all)
17+
* - `filter` / `search` — scoped selection
18+
* - `match_all` — explicit opt-in to target every rule
1819
*/
1920
export const bulkOperationParamsSchema = z
2021
.object({
@@ -30,12 +31,27 @@ export const bulkOperationParamsSchema = z
3031
.describe(
3132
`KQL filter string to match rules. At most ${BULK_FILTER_MAX_RULES} matching rules are processed per request.`
3233
),
34+
search: z
35+
.string()
36+
.optional()
37+
.describe('Free-text search string to match rules by name and description.'),
38+
match_all: z
39+
.literal(true)
40+
.optional()
41+
.describe('When true, targets all rules. Cannot be combined with ids.'),
3342
})
34-
.refine((data) => data.ids != null || data.filter != null, {
35-
message: 'Either ids or filter must be provided.',
43+
.refine(
44+
(data) =>
45+
data.ids != null || data.filter != null || data.search != null || data.match_all === true,
46+
{ message: 'At least one of ids, filter, search, or match_all must be provided.' }
47+
)
48+
.refine((data) => data.ids == null || (data.filter == null && data.search == null), {
49+
message: 'ids cannot be combined with filter or search.',
3650
})
37-
.refine((data) => data.ids == null || data.filter == null, {
38-
message: 'Only one of ids or filter can be provided.',
39-
});
51+
.refine(
52+
(data) =>
53+
data.match_all == null || (data.ids == null && data.filter == null && data.search == null),
54+
{ message: 'match_all cannot be combined with ids, filter, or search.' }
55+
);
4056

4157
export type BulkOperationParams = z.infer<typeof bulkOperationParamsSchema>;

x-pack/platform/plugins/shared/alerting_v2/common/utils/build_rules_list_kql.test.ts

Lines changed: 0 additions & 63 deletions
This file was deleted.

x-pack/platform/plugins/shared/alerting_v2/common/utils/build_rules_list_kql.ts

Lines changed: 0 additions & 53 deletions
This file was deleted.

x-pack/platform/plugins/shared/alerting_v2/public/hooks/use_bulk_select.test.ts

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,17 @@ describe('useBulkSelect', () => {
6666
expect(result.current.selectedCount).toBe(logical);
6767
});
6868

69-
it('returns match-all filter when select-all with no exclusions', () => {
69+
it('returns match_all params when select-all with no filter or search', () => {
7070
const { result } = renderHook(() => useBulkSelect({ totalItemCount: 10, items: pageItems }));
7171

7272
act(() => {
7373
result.current.onSelectAll();
7474
});
7575

76-
expect(result.current.getBulkParams()).toEqual({ filter: '' });
76+
expect(result.current.getBulkParams()).toEqual({ match_all: true });
7777
});
7878

79-
it('scopes select-all bulk filter to filter', () => {
79+
it('scopes select-all bulk params to filter', () => {
8080
const { result } = renderHook(() =>
8181
useBulkSelect({
8282
totalItemCount: 10,
@@ -89,10 +89,10 @@ describe('useBulkSelect', () => {
8989
result.current.onSelectAll();
9090
});
9191

92-
expect(result.current.getBulkParams()).toEqual({ filter: 'enabled: true' });
92+
expect(result.current.getBulkParams()).toEqual({ filter: '(enabled: true)' });
9393
});
9494

95-
it('scopes select-all bulk filter to search', () => {
95+
it('passes search as a separate field in bulk params', () => {
9696
const { result } = renderHook(() =>
9797
useBulkSelect({
9898
totalItemCount: 10,
@@ -105,13 +105,10 @@ describe('useBulkSelect', () => {
105105
result.current.onSelectAll();
106106
});
107107

108-
expect(result.current.getBulkParams()).toEqual({
109-
filter:
110-
'(metadata.name: prod* OR metadata.description: prod* OR metadata.tags: prod* OR grouping.fields: prod*)',
111-
});
108+
expect(result.current.getBulkParams()).toEqual({ search: 'prod' });
112109
});
113110

114-
it('combines list scope with NOT exclusions when select-all with deselected rows', () => {
111+
it('passes filter and search as separate fields with exclusions', () => {
115112
const { result } = renderHook(() =>
116113
useBulkSelect({
117114
totalItemCount: 10,
@@ -129,8 +126,30 @@ describe('useBulkSelect', () => {
129126
});
130127

131128
expect(result.current.getBulkParams()).toEqual({
132-
filter:
133-
'((enabled: true) AND ((metadata.name: x* OR metadata.description: x* OR metadata.tags: x* OR grouping.fields: x*))) AND NOT (id: "rule-1")',
129+
filter: '(enabled: true) AND NOT (id: "rule-1")',
130+
search: 'x',
131+
});
132+
});
133+
134+
it('includes only exclusion clauses in filter when no structural filter is set', () => {
135+
const { result } = renderHook(() =>
136+
useBulkSelect({
137+
totalItemCount: 10,
138+
items: pageItems,
139+
search: 'prod',
140+
})
141+
);
142+
143+
act(() => {
144+
result.current.onSelectAll();
145+
});
146+
act(() => {
147+
result.current.onSelectRow('rule-1');
148+
});
149+
150+
expect(result.current.getBulkParams()).toEqual({
151+
filter: 'NOT (id: "rule-1")',
152+
search: 'prod',
134153
});
135154
});
136155

x-pack/platform/plugins/shared/alerting_v2/public/hooks/use_bulk_select.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import { useReducer, useMemo, useCallback } from 'react';
99
import { BULK_FILTER_MAX_RULES } from '@kbn/alerting-v2-schemas';
1010
import { escapeQuotes } from '@kbn/es-query';
11-
import { buildApiRulesListCombinedFilter } from '../../common/utils/build_rules_list_kql';
1211
import type { BulkOperationParams } from '../services/rules_api';
1312

1413
interface BulkSelectState {
@@ -176,24 +175,33 @@ export const useBulkSelect = ({ totalItemCount, items, filter, search }: UseBulk
176175
/**
177176
* Returns the appropriate `BulkOperationParams` for the current selection state.
178177
*
179-
* When `isAllSelected` is true, sends a filter (or empty filter for "match all")
178+
* When `isAllSelected` is true, sends `filter` and/or `search` params
180179
* with an exclusion clause for deselected IDs. When false, sends explicit IDs.
181180
*
182181
* Filters use clean API field names (e.g. `id`), which the server
183182
* translates to the saved-object KQL format before querying.
184183
*/
185184
const getBulkParams = useCallback((): BulkOperationParams => {
186185
if (state.isAllSelected) {
187-
const listScope = buildApiRulesListCombinedFilter({ filter, search }) ?? '';
188186
const excludedIds = [...state.selectedIds];
189-
if (excludedIds.length === 0) {
190-
return { filter: listScope };
191-
}
192-
const exclusionClauses = excludedIds.map((id) => `id: "${escapeQuotes(id)}"`).join(' or ');
193-
if (listScope) {
194-
return { filter: `(${listScope}) AND NOT (${exclusionClauses})` };
195-
}
196-
return { filter: `NOT (${exclusionClauses})` };
187+
const exclusionClauses =
188+
excludedIds.length > 0
189+
? excludedIds.map((id) => `id: "${escapeQuotes(id)}"`).join(' or ')
190+
: undefined;
191+
192+
const wrappedFilter = filter ? `(${filter})` : undefined;
193+
const combinedFilter = [
194+
wrappedFilter,
195+
exclusionClauses ? `NOT (${exclusionClauses})` : undefined,
196+
]
197+
.filter(Boolean)
198+
.join(' AND ');
199+
200+
return {
201+
...(combinedFilter ? { filter: combinedFilter } : {}),
202+
...(search ? { search } : {}),
203+
...(!combinedFilter && !search ? { match_all: true as const } : {}),
204+
};
197205
}
198206

199207
return { ids: [...state.selectedIds] };

x-pack/platform/plugins/shared/alerting_v2/public/pages/rules_list_page/rules_list_table_container.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ describe('RulesListTableContainer', () => {
390390
fireEvent.click(screen.getByTestId('bulkEnableRules'));
391391

392392
expect(mockBulkEnableMutate).toHaveBeenCalledWith(
393-
{ filter: '' },
393+
{ match_all: true },
394394
expect.objectContaining({ onSuccess: expect.any(Function) })
395395
);
396396
});
@@ -420,7 +420,7 @@ describe('RulesListTableContainer', () => {
420420
fireEvent.click(screen.getByTestId('bulkEnableRules'));
421421

422422
expect(mockBulkEnableMutate).toHaveBeenCalledWith(
423-
{ filter: 'kind: alert' },
423+
{ filter: '(kind: alert)' },
424424
expect.objectContaining({ onSuccess: expect.any(Function) })
425425
);
426426
});

x-pack/platform/plugins/shared/alerting_v2/server/lib/rules_client/build_rule_search.test.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.

x-pack/platform/plugins/shared/alerting_v2/server/lib/rules_client/build_rule_search.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)