Skip to content

Conversation

@baileycash-elastic
Copy link
Contributor

@baileycash-elastic baileycash-elastic commented Oct 29, 2025

Summary

This PR enhances the existing bulk update api for alerts to:

  1. Make updating workflow_status (status) optional
  2. Introduce the ability to add or remove workflow_tags from alert documents (tags)
  3. Modify page size for mget requests when retrieving alert documents to reduce wait time
  4. Disallow passing empty query strings

@github-actions github-actions bot added the author:obs-ux-management PRs authored by the obs ux management team label Oct 29, 2025
@baileycash-elastic
Copy link
Contributor Author

/ci

@baileycash-elastic baileycash-elastic marked this pull request as ready for review October 30, 2025 23:57
@baileycash-elastic baileycash-elastic requested review from a team as code owners October 30, 2025 23:57
@baileycash-elastic baileycash-elastic added backport:skip This PR does not require backporting release_note:feature Makes this part of the condensed release notes Team:actionable-obs Formerly "obs-ux-management", responsible for SLO, o11y alerting, significant events, & synthetics. labels Oct 30, 2025
@elasticmachine
Copy link
Contributor

Pinging @elastic/obs-ux-management-team (Team:obs-ux-management)

@baileycash-elastic baileycash-elastic added the Team:ResponseOps Platform ResponseOps team (formerly the Cases and Alerting teams) t// label Oct 30, 2025
@elasticmachine
Copy link
Contributor

Pinging @elastic/response-ops (Team:ResponseOps)

@baileycash-elastic baileycash-elastic added release_note:skip Skip the PR/issue when compiling release notes and removed release_note:feature Makes this part of the condensed release notes labels Oct 31, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR enhances the bulk update alerts functionality by adding support for adding and removing tags, making the status parameter optional, and refactoring the implementation to use Painless scripts with parameters. The changes enable more flexible alert management operations.

Key Changes

  • Added support for addTags and removeTags operations in bulk alert updates
  • Made the status parameter optional in bulk update operations
  • Refactored update logic to use parameterized Painless scripts for better security
  • Extracted Painless update scripts into a separate utility file for reusability

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
alert_client_bulk_update_scripts.ts New utility file containing Painless scripts for status and tag updates
bulk_update_alerts.ts Updated route validation to support optional status and new tag operations using t.intersection and t.partial
alerts_client.ts Refactored bulkUpdate method to build dynamic scripts, replaced mgetAlertsAuditOperateStatus with inline implementation using scripts
alerts_client.test.ts Added comprehensive test coverage for tag operations and edge cases

}

if (bulkUpdateRequest.length === 0) {
return;
Copy link

Copilot AI Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When all alerts are not found (bulkUpdateRequest.length === 0), the method returns undefined without indicating whether this is an error condition. Consider returning a more informative response object (e.g., { updated: 0, errors: [] }) or logging a warning to help with debugging and monitoring.

Suggested change
return;
this.logger.warn(
`bulkUpdate: No alerts found to update for ids=[${ids.join(', ')}] in index=${index}`
);
return { updated: 0, errors: [], message: 'No alerts found to update.' };

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should be a debug log rather than a warn for a 404

Comment on lines +843 to +844
// @ts-expect-error doesn't handle error branch in MGetResponse
if (item.found) {
Copy link

Copilot AI Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @ts-expect-error comment indicates a typing issue with MGetResponse. Consider using a type guard or proper type assertion instead of suppressing the error. For example: if ('found' in item && item.found) or use a proper type predicate to handle the union type correctly.

Suggested change
// @ts-expect-error doesn't handle error branch in MGetResponse
if (item.found) {
// Only process items where the document was found
if ('found' in item && item.found) {

Copilot uses AI. Check for mistakes.
@cnasikas cnasikas requested a review from adcoelho November 3, 2025 12:52
Comment on lines 22 to 57
t.union([
t.strict({
status: t.union([
t.literal('open'),
t.literal('closed'),
t.literal('in-progress'), // TODO: remove after migration to acknowledged
t.literal('acknowledged'),
]),
index: t.string,
ids: t.array(t.string),
query: t.undefined,
}),
t.strict({
status: t.union([
t.literal('open'),
t.literal('closed'),
t.literal('in-progress'), // TODO: remove after migration to acknowledged
t.literal('acknowledged'),
]),
index: t.string,
ids: t.undefined,
query: t.union([t.object, t.string]),
}),
t.intersection([
t.strict({
index: t.string,
ids: t.array(t.string),
query: t.undefined,
}),
t.partial({
status: t.union([
t.literal('open'),
t.literal('closed'),
t.literal('in-progress'), // TODO: remove after migration to acknowledged
t.literal('acknowledged'),
]),
addTags: t.array(t.string),
removeTags: t.array(t.string),
}),
]),
t.intersection([
t.strict({
index: t.string,
ids: t.undefined,
query: t.union([t.object, t.string]),
}),
t.partial({
status: t.union([
t.literal('open'),
t.literal('closed'),
t.literal('in-progress'), // TODO: remove after migration to acknowledged
t.literal('acknowledged'),
]),
addTags: t.array(t.string),
removeTags: t.array(t.string),
}),
]),
])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a few comments about this change 😅

1 - I am a bit rusty with io-ts, but how about the following to eliminate duplication?

Suggested change
t.union([
t.strict({
status: t.union([
t.literal('open'),
t.literal('closed'),
t.literal('in-progress'), // TODO: remove after migration to acknowledged
t.literal('acknowledged'),
]),
index: t.string,
ids: t.array(t.string),
query: t.undefined,
}),
t.strict({
status: t.union([
t.literal('open'),
t.literal('closed'),
t.literal('in-progress'), // TODO: remove after migration to acknowledged
t.literal('acknowledged'),
]),
index: t.string,
ids: t.undefined,
query: t.union([t.object, t.string]),
}),
t.intersection([
t.strict({
index: t.string,
ids: t.array(t.string),
query: t.undefined,
}),
t.partial({
status: t.union([
t.literal('open'),
t.literal('closed'),
t.literal('in-progress'), // TODO: remove after migration to acknowledged
t.literal('acknowledged'),
]),
addTags: t.array(t.string),
removeTags: t.array(t.string),
}),
]),
t.intersection([
t.strict({
index: t.string,
ids: t.undefined,
query: t.union([t.object, t.string]),
}),
t.partial({
status: t.union([
t.literal('open'),
t.literal('closed'),
t.literal('in-progress'), // TODO: remove after migration to acknowledged
t.literal('acknowledged'),
]),
addTags: t.array(t.string),
removeTags: t.array(t.string),
}),
]),
])
t.intersection([
t.exact(
t.type({
index: t.string,
})
),
t.union([
t.exact(
t.type({
ids: t.array(t.string),
query: t.undefined,
})
),
t.exact(
t.type({
ids: t.undefined,
query: t.union([t.object, t.string]),
})
),
]),
t.partial({
status: t.union([
t.literal('open'),
t.literal('closed'),
t.literal('in-progress'), // TODO: remove after migration to acknowledged
t.literal('acknowledged'),
]),
tags: t.array(t.string),
}),
])

2 - Could you please add some tests to validate this? Since there isn't a dedicated schema file, we could do mock calls to this route and make sure all combinations of fields work as expected.
3 - I don't think we should have addTags and removeTags. (I changed it to just tags in the schema.)

A single tags field leads to more predictable behaviour from the api and I think will make our lives much easier. What is passed is what is saved, and we can validate that field immediately. The user also does not need to know the current state to make the updates they need.

With addTags and removeTags, off the top of my head:

  • We need to always fetch the SO first to calculate the new list
  • We need to pick an order to apply these. Do we add or remove first?
  • We need to validate before applying
  • We need to validate again after applying
  • If using Terraform, the update operation would prob not work(how to calculate add and remove if someone just edits the tf file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@elasticmachine
Copy link
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Test Failures

  • [job] [logs] FTR Configs #27 / discover Discover CSV Export Generate CSV: new search generates a large export
  • [job] [logs] Scout Test Run Builder / serverless-security - EUI testing wrapper: EuiDataGrid - data grid, run

Metrics [docs]

✅ unchanged

History

cnasikas added a commit that referenced this pull request Nov 22, 2025
Closes #240356
Replaces [this PR](#241051)

## Summary

This pull request introduces a new API route and supporting backend
logic for bulk updating tags on alerts in the rule registry. The main
changes include the addition of a `patchTags` method to the
`AlertsClient`, a new route for bulk patching alert tags, and reusable
scripts for tag updates. These improvements make it possible to
add/remove or replace tags on multiple alerts efficiently, either by IDs
or by query.

### Alerting authorization
The `AlertingAuthorization` class changed to support bulk authorized
multiple rule type IDs and consumers. The `ensureAuthorized` was renamed
to `_ensureAuthorized` and accepts `Array<{ ruleTypeId: string;
consumers: string[] }>;`. The logic is the same as before, but it
constructs all security actions based on the input. This is needed to
avoid having to do one authorization call per `(ruleTypeId, consumer)`
pair. Lastly, a `bulkEnsureAuthorized` is exposed which is a wrapper of
the private `_ensureAuthorized`.

### Alerts client
The code in the `AlertsClient` is pretty outdated. For this reason, I
decided not to use the existing functionality and code from scratch. The
`bulkUpdateTags` is introduced, and it bulk updates the tags of multiple
alerts either by using `alertIds` or by using a KQL `query`. In the
first scenario, an aggregation is made to get the rule type ID and the
consumer of each alert. Then we bulk authorize. If the user has access,
we move forward and update the tags of the alerts. If not, we throw an
error. For the `query` scenario, we apply the authorization filter along
with the `query` to filter out the alerts that the user does not have
access to. Lastly, we audit log only once for the whole bulk operation
and not for each alert found.

### API and Backend Enhancements

* Added a new API route `POST /internal/rac/alerts/tags` for bulk
updating alert tags, supporting add, remove, and replace operations,
with validation and error handling.
[[1]](diffhunk://#diff-00a4668b8046bb9c2d423b91818e04ee9532682eecf426e5a80bce35276b0bd8R1-R113)
[[2]](diffhunk://#diff-0abcdbe7de6b3dc00d522a6673ff7bcd99d0f4bf2d6002c59d475e85747d0970R19-R26)
* Implemented the `bulkUpdateTags` method in `AlertsClient`, enabling
tag updates on alerts by IDs or query, using Elasticsearch scripts for
efficient bulk operations.

### Reusable Update Scripts

* Added reusable Painless scripts for adding, removing, and replacing
alert tags, exported from `alert_client_bulk_update_scripts.ts` and
integrated into the client logic.
[[1]](diffhunk://#diff-7c530f4d6aa0a6b9f0ca63714468130690bc5fa7e33a91591849318c1f380f7dR1-R34)
[[2]](diffhunk://#diff-bbccd80f85af0d00f6eafdbc9e444164e6fee357697dc7be42e978fd376a357cR74-R78)

---------

Co-authored-by: Elastic Machine <[email protected]>
Co-authored-by: Christos Nasikas <[email protected]>
Co-authored-by: kibanamachine <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author:obs-ux-management PRs authored by the obs ux management team backport:skip This PR does not require backporting release_note:skip Skip the PR/issue when compiling release notes Team:actionable-obs Formerly "obs-ux-management", responsible for SLO, o11y alerting, significant events, & synthetics. Team:ResponseOps Platform ResponseOps team (formerly the Cases and Alerting teams) t//

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants