@@ -16,7 +16,6 @@ import { isEqual } from 'lodash';
1616import objectHash from 'object-hash' ;
1717import pLimit from 'p-limit' ;
1818import {
19- LEGACY_RULE_BACKED_FALLBACK ,
2019 type Query ,
2120 type QueryLink ,
2221 type QueryLinkRequest ,
@@ -45,6 +44,10 @@ import { computeRuleId } from './helpers/query';
4544
4645type TermQueryFieldValue = string | boolean | number | null ;
4746
47+ export interface QueryLinkFilters {
48+ ruleBacked ?: boolean ;
49+ }
50+
4851interface TermQueryOpts {
4952 queryEmptyString : boolean ;
5053}
@@ -61,6 +64,30 @@ function termQuery<T extends string>(
6164 return [ { term : { [ field ] : value } } ] ;
6265}
6366
67+ function ruleBackedFilter ( value : boolean | undefined ) : QueryDslQueryContainer [ ] {
68+ if ( value === undefined ) {
69+ return [ ] ;
70+ }
71+
72+ if ( ! value ) {
73+ return termQuery ( RULE_BACKED , false ) ;
74+ }
75+
76+ // When filtering for rule-backed queries, also include legacy docs
77+ // that predate the rule_backed field.
78+ return [
79+ {
80+ bool : {
81+ should : [
82+ { term : { [ RULE_BACKED ] : true } } ,
83+ { bool : { must_not : [ { exists : { field : RULE_BACKED } } ] } } ,
84+ ] ,
85+ minimum_should_match : 1 ,
86+ } ,
87+ } ,
88+ ] ;
89+ }
90+
6491function termsQuery < T extends string > (
6592 field : T ,
6693 values : Array < TermQueryFieldValue | undefined > | null | undefined
@@ -114,7 +141,6 @@ type QueryLinkStorageFields = Omit<QueryLink, 'query' | 'stream_name'> & {
114141 [ QUERY_KQL_BODY ] : string ;
115142 [ QUERY_ESQL_QUERY ] : string ;
116143 [ QUERY_SEVERITY_SCORE ] ?: number ;
117- [ RULE_BACKED ] ?: boolean ;
118144} ;
119145
120146export type StoredQueryLink = QueryLinkStorageFields & {
@@ -136,18 +162,16 @@ function fromStorage(link: StoredQueryLink): QueryLink {
136162 [ QUERY_FEATURE_FILTER ] : string ;
137163 [ QUERY_FEATURE_TYPE ] : 'system' ;
138164 [ QUERY_EVIDENCE ] ?: string [ ] ;
139- [ RULE_BACKED ] ?: boolean ;
140165 } = link as StoredQueryLink & {
141166 [ QUERY_FEATURE_NAME ] : string ;
142167 [ QUERY_FEATURE_FILTER ] : string ;
143168 [ QUERY_FEATURE_TYPE ] : 'system' ;
144169 [ QUERY_EVIDENCE ] ?: string [ ] ;
145- [ RULE_BACKED ] ?: boolean ;
146170 } ;
147171 return {
148172 ...storageFields ,
149173 stream_name : link [ STREAM_NAME ] ,
150- rule_backed : storageFields [ RULE_BACKED ] ?? LEGACY_RULE_BACKED_FALLBACK ,
174+ rule_backed : storageFields [ RULE_BACKED ] ,
151175 rule_id : storageFields [ RULE_ID ] ,
152176 query : {
153177 id : storageFields [ ASSET_ID ] ,
@@ -171,15 +195,9 @@ function fromStorage(link: StoredQueryLink): QueryLink {
171195 } satisfies QueryLink ;
172196}
173197
174- type QueryLinkRequestWithRuleBacked = QueryLinkRequest & { rule_backed ?: boolean } ;
175-
176- function toStorage (
177- definition : Streams . all . Definition ,
178- request : QueryLinkRequestWithRuleBacked
179- ) : StoredQueryLink {
198+ function toStorage ( definition : Streams . all . Definition , request : QueryLinkRequest ) : StoredQueryLink {
180199 const link = toQueryLink ( definition , request ) ;
181200 const { query, stream_name, ...rest } = link ;
182- const ruleBacked = request . rule_backed ?? LEGACY_RULE_BACKED_FALLBACK ;
183201 return {
184202 ...rest ,
185203 [ STREAM_NAME ] : definition . name ,
@@ -191,7 +209,7 @@ function toStorage(
191209 [ QUERY_FEATURE_TYPE ] : query . feature ? query . feature . type : '' ,
192210 [ QUERY_SEVERITY_SCORE ] : query . severity_score ,
193211 [ QUERY_EVIDENCE ] : query . evidence ,
194- [ RULE_BACKED ] : ruleBacked ,
212+ [ RULE_BACKED ] : request . rule_backed ,
195213 [ RULE_ID ] : link . rule_id ,
196214 } as StoredQueryLink ;
197215}
@@ -203,14 +221,23 @@ function hasBreakingChange(currentQuery: StreamQuery, nextQuery: StreamQuery): b
203221 ) ;
204222}
205223
206- function toQueryLinkFromQuery ( query : StreamQuery , stream : string ) : QueryLink {
224+ function toQueryLinkFromQuery ( {
225+ query,
226+ stream,
227+ ruleBacked = true ,
228+ } : {
229+ query : StreamQuery ;
230+ stream : string ;
231+ ruleBacked ?: boolean ;
232+ } ) : QueryLink {
207233 const assetUuid = getQueryLinkUuid ( stream , { 'asset.type' : 'query' , 'asset.id' : query . id } ) ;
208234 return {
209235 'asset.uuid' : assetUuid ,
210236 'asset.type' : 'query' ,
211237 'asset.id' : query . id ,
212238 query,
213239 stream_name : stream ,
240+ rule_backed : ruleBacked ,
214241 rule_id : computeRuleId ( assetUuid , query . esql . query ) ,
215242 } ;
216243}
@@ -230,7 +257,7 @@ export class QueryClient {
230257
231258 async syncQueryList (
232259 definition : Streams . all . Definition ,
233- links : QueryLinkRequestWithRuleBacked [ ]
260+ links : QueryLinkRequest [ ]
234261 ) : Promise < { deleted : QueryLink [ ] ; indexed : QueryLink [ ] } > {
235262 const name = definition . name ;
236263 const assetsResponse = await this . dependencies . storageClient . search ( {
@@ -313,8 +340,12 @@ export class QueryClient {
313340 * Returns all query links for given streams or
314341 * all query links if no stream names are provided.
315342 */
316- async getQueryLinks ( streamNames : string [ ] ) : Promise < QueryLink [ ] > {
317- const filter = [ ...termsQuery ( STREAM_NAME , streamNames ) , ...termQuery ( ASSET_TYPE , 'query' ) ] ;
343+ async getQueryLinks ( streamNames : string [ ] , filters ?: QueryLinkFilters ) : Promise < QueryLink [ ] > {
344+ const filter = [
345+ ...termsQuery ( STREAM_NAME , streamNames ) ,
346+ ...termQuery ( ASSET_TYPE , 'query' ) ,
347+ ...ruleBackedFilter ( filters ?. ruleBacked ) ,
348+ ] ;
318349
319350 const queriesResponse = await this . dependencies . storageClient . search ( {
320351 size : 10_000 ,
@@ -334,23 +365,7 @@ export class QueryClient {
334365 * Used internally by promoteQueries.
335366 */
336367 private async getUnbackedQueries ( streamName : string ) : Promise < QueryLink [ ] > {
337- const filter = [
338- ...termQuery ( STREAM_NAME , streamName ) ,
339- ...termQuery ( ASSET_TYPE , 'query' ) ,
340- ...termQuery ( RULE_BACKED , false ) ,
341- ] ;
342-
343- const assetsResponse = await this . dependencies . storageClient . search ( {
344- size : 10_000 ,
345- track_total_hits : false ,
346- query : {
347- bool : {
348- filter,
349- } ,
350- } ,
351- } ) ;
352-
353- return assetsResponse . hits . hits . map ( ( hit ) => fromStorage ( hit . _source ) ) ;
368+ return this . getQueryLinks ( [ streamName ] , { ruleBacked : false } ) ;
354369 }
355370
356371 /**
@@ -377,19 +392,7 @@ export class QueryClient {
377392 * Returns all query links across streams that do not have a backing Kibana rule.
378393 */
379394 async getAllUnbackedQueries ( ) : Promise < QueryLink [ ] > {
380- const filter = [ ...termQuery ( ASSET_TYPE , 'query' ) , ...termQuery ( RULE_BACKED , false ) ] ;
381-
382- const assetsResponse = await this . dependencies . storageClient . search ( {
383- size : 10_000 ,
384- track_total_hits : false ,
385- query : {
386- bool : {
387- filter,
388- } ,
389- } ,
390- } ) ;
391-
392- return assetsResponse . hits . hits . map ( ( hit ) => fromStorage ( hit . _source ) ) ;
395+ return this . getQueryLinks ( [ ] , { ruleBacked : false } ) ;
393396 }
394397
395398 async bulkGetByIds ( name : string , ids : string [ ] ) {
@@ -413,8 +416,16 @@ export class QueryClient {
413416 return assetsResponse . hits . hits . map ( ( hit ) => fromStorage ( hit . _source ) ) ;
414417 }
415418
416- async findQueries ( streamNames : string [ ] , query : string ) : Promise < QueryLink [ ] > {
417- const filter = [ ...termsQuery ( STREAM_NAME , streamNames ) , ...termQuery ( ASSET_TYPE , 'query' ) ] ;
419+ async findQueries (
420+ streamNames : string [ ] ,
421+ query : string ,
422+ filters ?: QueryLinkFilters
423+ ) : Promise < QueryLink [ ] > {
424+ const filter = [
425+ ...termsQuery ( STREAM_NAME , streamNames ) ,
426+ ...termQuery ( ASSET_TYPE , 'query' ) ,
427+ ...ruleBackedFilter ( filters ?. ruleBacked ) ,
428+ ] ;
418429
419430 const assetsResponse = await this . dependencies . storageClient . search ( {
420431 size : 10_000 ,
@@ -446,7 +457,7 @@ export class QueryClient {
446457 if ( 'index' in operation ) {
447458 const document = toStorage (
448459 definition ,
449- Object . values ( operation ) [ 0 ] . asset as QueryLinkRequestWithRuleBacked
460+ Object . values ( operation ) [ 0 ] . asset as QueryLinkRequest
450461 ) ;
451462 return {
452463 index : {
@@ -482,6 +493,7 @@ export class QueryClient {
482493 query : link . query ,
483494 title : link . query . title ,
484495 stream_name : link . stream_name ,
496+ rule_backed : link . rule_backed ,
485497 rule_id : link . rule_id ,
486498 } ;
487499 } ) ;
@@ -520,11 +532,11 @@ export class QueryClient {
520532 for ( const query of queries ) {
521533 const currentLink = currentLinkByQueryId . get ( query . id ) ;
522534 if ( ! currentLink ) {
523- const link = toQueryLinkFromQuery ( query , stream ) ;
535+ const link = toQueryLinkFromQuery ( { query, stream } ) ;
524536 nextQueriesToCreate . push ( link ) ;
525537 allNextQueryLinks . push ( link ) ;
526538 } else if ( hasBreakingChange ( currentLink . query , query ) ) {
527- const link = toQueryLinkFromQuery ( query , stream ) ;
539+ const link = toQueryLinkFromQuery ( { query, stream } ) ;
528540 nextQueriesUpdatedWithBreakingChange . push ( link ) ;
529541 allNextQueryLinks . push ( link ) ;
530542 } else {
@@ -638,28 +650,23 @@ export class QueryClient {
638650 ...operations
639651 . filter ( ( operation ) => operation . index && ! currentIds . has ( operation . index ! . id ) )
640652 . map ( ( operation ) =>
641- toQueryLinkFromQuery (
642- {
653+ toQueryLinkFromQuery ( {
654+ query : {
643655 ...operation . index ! ,
644656 esql : {
645657 query : buildEsqlQuery ( indices , operation . index ! ) ,
646658 } ,
647659 } ,
648- stream
649- )
660+ stream,
661+ ruleBacked : options ?. createRules !== false ,
662+ } )
650663 ) ,
651664 ] ;
652665
653666 if ( options ?. createRules === false ) {
654- const nextQueriesWithRuleBacked = nextQueries . map ( ( link ) => ( {
655- ...link ,
656- rule_backed : currentIds . has ( link . query . id )
657- ? link . rule_backed ?? LEGACY_RULE_BACKED_FALLBACK
658- : false ,
659- } ) ) ;
660667 await this . syncQueryList (
661668 definition ,
662- nextQueriesWithRuleBacked . map ( ( link ) => ( {
669+ nextQueries . map ( ( link ) => ( {
663670 [ ASSET_ID ] : link [ ASSET_ID ] ,
664671 [ ASSET_TYPE ] : link [ ASSET_TYPE ] ,
665672 query : link . query ,
@@ -696,7 +703,7 @@ export class QueryClient {
696703 const idSet = new Set ( queryIds ) ;
697704 const toPromote = unbacked
698705 . filter ( ( link ) => idSet . has ( link . query . id ) )
699- . map ( ( link ) => toQueryLinkFromQuery ( link . query , streamName ) ) ;
706+ . map ( ( link ) => toQueryLinkFromQuery ( { query : link . query , stream : streamName } ) ) ;
700707
701708 if ( toPromote . length === 0 ) {
702709 return { promoted : 0 } ;
0 commit comments