@@ -70,6 +70,11 @@ import { getRuleTypeIdsFilter } from '../lib/get_rule_type_ids_filter';
7070import { getConsumersFilter } from '../lib/get_consumers_filter' ;
7171import { mergeUniqueFieldsByName } from '../utils/unique_fields' ;
7272import { getAlertFieldsFromIndexFetcher } from '../utils/get_alert_fields_from_index_fetcher' ;
73+ import {
74+ ADD_TAGS_UPDATE_SCRIPT ,
75+ getStatusUpdateScript ,
76+ REMOVE_TAGS_UPDATE_SCRIPT ,
77+ } from '../utils/alert_client_bulk_update_scripts' ;
7378import type { GetAlertFieldsResponseV1 } from '../routes/get_alert_fields' ;
7479
7580// TODO: Fix typings https://github.com/elastic/kibana/issues/101776
@@ -113,9 +118,11 @@ export interface UpdateOptions<Params extends RuleTypeParams> {
113118
114119export interface BulkUpdateOptions < Params extends RuleTypeParams > {
115120 ids ?: string [ ] | null ;
116- status : STATUS_VALUES ;
121+ status ? : STATUS_VALUES ;
117122 index : string ;
118123 query ?: object | string | null ;
124+ addTags ?: string [ ] ;
125+ removeTags ?: string [ ] ;
119126}
120127
121128interface MgetAndAuditAlert {
@@ -442,25 +449,6 @@ export class AlertsClient {
442449 }
443450 }
444451
445- /**
446- * When an update by ids is requested, do a multi-get, ensure authz and audit alerts, then execute bulk update
447- */
448- private async mgetAlertsAuditOperateStatus ( {
449- alerts,
450- status,
451- operation,
452- } : {
453- alerts : MgetAndAuditAlert [ ] ;
454- status : STATUS_VALUES ;
455- operation : ReadOperations . Find | ReadOperations . Get | WriteOperations . Update ;
456- } ) {
457- return this . mgetAlertsAuditOperate ( {
458- alerts,
459- operation,
460- fieldToUpdate : ( source ) => this . getAlertStatusFieldUpdate ( source , status ) ,
461- } ) ;
462- }
463-
464452 private async buildEsQueryWithAuthz (
465453 query : object | string | null | undefined ,
466454 id : string | null | undefined ,
@@ -810,15 +798,70 @@ export class AlertsClient {
810798 query,
811799 index,
812800 status,
801+ addTags,
802+ removeTags,
813803 } : BulkUpdateOptions < Params > ) {
804+ const scriptOps : string [ ] = [ ] ;
805+ const params : Record < string , any > = { } ;
806+
807+ if ( status != null ) {
808+ scriptOps . push ( getStatusUpdateScript ( status ) ) ;
809+ }
810+
811+ if ( addTags != null && addTags . length > 0 ) {
812+ params . addTags = addTags ;
813+ scriptOps . push ( ADD_TAGS_UPDATE_SCRIPT ) ;
814+ }
815+
816+ if ( removeTags != null && removeTags . length > 0 ) {
817+ params . removeTags = removeTags ;
818+ scriptOps . push ( REMOVE_TAGS_UPDATE_SCRIPT ) ;
819+ }
820+
821+ if ( scriptOps . length === 0 ) {
822+ return ;
823+ }
824+
825+ const script = {
826+ source : scriptOps . join ( '\n' ) ,
827+ lang : 'painless' ,
828+ params,
829+ } ;
830+
814831 // rejects at the route level if more than 1000 id's are passed in
815832 if ( ids != null ) {
816833 const alerts = ids . map ( ( id ) => ( { id, index } ) ) ;
817- return this . mgetAlertsAuditOperateStatus ( {
834+ const mgetRes = await this . ensureAllAlertsAuthorized ( {
818835 alerts,
819- status,
820836 operation : WriteOperations . Update ,
821837 } ) ;
838+
839+ const bulkUpdateRequest = [ ] ;
840+
841+ for ( const item of mgetRes . docs ) {
842+ // @ts -expect-error doesn't handle error branch in MGetResponse
843+ if ( item . found ) {
844+ bulkUpdateRequest . push (
845+ {
846+ update : {
847+ _index : item . _index ,
848+ _id : item . _id ,
849+ } ,
850+ } ,
851+ { script }
852+ ) ;
853+ }
854+ }
855+
856+ if ( bulkUpdateRequest . length === 0 ) {
857+ return ;
858+ }
859+
860+ const bulkUpdateResponse = await this . esClient . bulk ( {
861+ refresh : 'wait_for' ,
862+ body : bulkUpdateRequest ,
863+ } ) ;
864+ return bulkUpdateResponse ;
822865 } else if ( query != null ) {
823866 try {
824867 // execute search after with query + authorization filter
@@ -835,18 +878,11 @@ export class AlertsClient {
835878
836879 // executes updateByQuery with query + authorization filter
837880 // used in the queryAndAuditAllAlerts function
881+
838882 const result = await this . esClient . updateByQuery ( {
839883 index,
840884 conflicts : 'proceed' ,
841- script : {
842- source : `if (ctx._source['${ ALERT_WORKFLOW_STATUS } '] != null) {
843- ctx._source['${ ALERT_WORKFLOW_STATUS } '] = '${ status } '
844- }
845- if (ctx._source.signal != null && ctx._source.signal.status != null) {
846- ctx._source.signal.status = '${ status } '
847- }` ,
848- lang : 'painless' ,
849- } ,
885+ script,
850886 query : fetchAndAuditResponse . authorizedQuery as Omit < QueryDslQueryContainer , 'script' > ,
851887 ignore_unavailable : true ,
852888 } ) ;
0 commit comments