Query Kibana Universal Alerting alert history in Discover
In Kibana Universal Alerting, use ES|QL in Discover to replay how an incident unfolded, see who acknowledged or snoozed an alert, measure time-to-acknowledge trends, or correlate alert history with other data in your environment. To check the current state of alerts instead, use the Alerts page.
For events with type: signal, including basic queries and using them as input to a rule that opens an alert, refer to Query rule events.
Use the following table to jump to the query you need:
| Query | What it returns | Stream |
|---|---|---|
| Reconstruct the lifecycle of a specific alert | Every evaluation for one alert, in chronological order | .rule-events |
| Find all currently active alerts | One row per alert currently in active state |
.rule-events |
| List all breaches for a specific rule | Every evaluation where a rule's condition was met | .rule-events |
| Identify evaluation gaps | Recent no_data rows that may point to a pipeline issue |
.rule-events |
| View the full triage history for an alert | Every action taken on one alert, in chronological order | .alert-actions |
| Find all acknowledgments in a time window | Every acknowledgment action across all alerts | .alert-actions |
| Check alert assignment state | Every assignment action and who it was assigned to | .alert-actions |
| Audit dispatcher outcomes for a rule | Notified, suppressed, and unmatched outcomes for a rule | .alert-actions |
| Find snoozed series | Active and historical snoozes, including who set them and when they expire | .alert-actions |
| Trace the full story of an incident | Both streams filtered or joined together for a complete incident timeline | Both |
Before you can query alert history in Discover, add the alert data streams as data views. Repeat these steps for each stream.
- Open Discover, and then open the data view menu.
- Select Create a data view.
- Give your data view a name, for example
.rule-eventsor.alert-actions. - In the Index pattern field, enter the data stream name:
.ds-.rule-events-*for rule evaluation history..ds-.alert-actions-*for triage actions recorded on alerts.
- Open the Timestamp field dropdown and select
@timestamp. - Select Save data view to Kibana.
For more details on data view options, refer to Data views.
Kibana writes one rule event to .rule-events for each matching row of a scheduled rule run. Kibana never overwrites these events, which means you can reconstruct the full history of any alert by querying all events that share the same episode.id. The following sections provide example queries for common scenarios.
Use the alert's episode.id to pull all of its evaluations in chronological order. This shows exactly how that one alert moved through its lifecycle states from open to close, without mixing in other alerts from the same series.
FROM .rule-events
// Scope to a single alert by its ID
| WHERE episode.id == "<alert-id>"
// Sort oldest-first to read the progression forward in time
| SORT @timestamp ASC
// Keep the fields most relevant to reading the lifecycle sequence
| KEEP @timestamp, status, episode.id, episode.status, episode.status_count
To pull evaluations across every alert in a series instead, filter by group_hash in place of episode.id.
Returns one row for each alert currently in active state, along with the timestamp of its most recent evaluation.
FROM .rule-events
// Only include rows where the alert lifecycle state is active
| WHERE episode.status == "active"
// Deduplicate to one row per alert, showing the most recent evaluation
| STATS latest = MAX(@timestamp) BY episode.id, group_hash
Returns every evaluation row where the rule met its condition for a given rule. Use this to understand how often and how severely a rule fires across all its series.
FROM .rule-events
// Filter to one rule and only rows where the condition was met
| WHERE rule.id == "my-rule-id" AND status == "breached"
// Return fields that identify severity, series context, and the rule-defined payload
| KEEP @timestamp, group_hash, severity, episode.id, episode.status, data
no_data rows appear when a rule with no-data handling configured finds no matching data during an evaluation cycle. Events with type: signal don't include no_data. A cluster of these rows can indicate a data pipeline issue or a misconfigured rule.
FROM .rule-events
// no_data means the rule ran but found nothing to evaluate against
| WHERE status == "no_data"
| SORT @timestamp DESC
| LIMIT 50
Kibana writes one document to .alert-actions for every action a user or the system takes on an alert. Use it to audit who did what, measure acknowledgment response times, or check current snooze and assignment state. The following sections provide example queries for common scenarios.
.alert-actions stores the alert ID and status as episode_id and episode_status. Replace alert_id and alert_status with those names in the following queries.
Returns all actions recorded for a single alert in chronological order. Use this to see the complete response sequence: who acknowledged it, whether a user snoozed it, and how it was eventually resolved. The Resolve action writes deactivate, and Unresolve writes activate.
FROM .alert-actions
// Scope to a single alert by its ID
| WHERE alert_id == "<alert-id>"
// Sort oldest-first to read the response sequence forward in time
| SORT @timestamp ASC
// Keep the fields most relevant to understanding what happened and who did it
| KEEP @timestamp, action_type, actor, alert_status, reason
Returns every acknowledgment action across all alerts. Useful for tracking team response activity or measuring time-to-acknowledge trends.
FROM .alert-actions
// Filter to acknowledgment actions only
| WHERE action_type == "ack"
| SORT @timestamp DESC
Returns all assign actions, showing which alerts carry an assignment and to whom. Use this to audit ownership or find unacknowledged handoffs.
FROM .alert-actions
// Filter to assignment actions only
| WHERE action_type == "assign"
// Return the fields that identify the alert, who assigned it, and the target user
| KEEP @timestamp, alert_id, actor, assignee_uid
Returns all dispatcher decisions for a rule, covering alerts that were notified, suppressed due to throttling, or didn't match to any action policy.
FROM .alert-actions
// Filter to one rule and only the three dispatcher outcome action types
| WHERE rule_id == "my-rule-id" AND action_type IN ("notified", "suppress", "unmatched")
| SORT @timestamp DESC
Returns all snooze actions, including which series a user snoozed, who snoozed it, and when the snooze expires. Use this to find active snoozes or audit snooze history.
FROM .alert-actions
// Filter to snooze actions only
| WHERE action_type == "snooze"
// Return the fields needed to identify the series, the actor, and the expiry time
| KEEP @timestamp, group_hash, alert_id, expiry, actor
To get the complete picture of an incident, filter both streams by the same identifier. Both streams share group_hash as a flat keyword, making it the most reliable join key. episode.id in .rule-events and alert_id in .alert-actions hold the same value but use different naming conventions: dot-notation in .rule-events and flat snake_case in .alert-actions.
Filter by alert_id to return user actions (ack, assign, deactivate, and similar) and notifications for one alert. Dispatcher-level entries might be missing because system-written action types (fire, suppress, unmatched, notified) key to group_hash and might not carry an alert_id. Filter by group_hash to include the complete dispatcher history.
- Run a
.rule-eventsquery to find theepisode.idorgroup_hashyou care about. - Query
.alert-actionsusing that value:
FROM .alert-actions
// Use group_hash to include dispatcher actions that may not carry an alert_id
| WHERE group_hash == "<group-hash>"
| SORT @timestamp ASC
| KEEP @timestamp, action_type, actor, alert_id, reason
If you need to join both streams in a single query, use LOOKUP JOIN. This requires configuring .alert-actions as a lookup index, which is an extra setup step beyond standard Discover analysis:
FROM .rule-events
// Only include rows that belong to an alert (events with type: signal have no episode.id)
| WHERE episode.id IS NOT NULL
// Rename to match the join key naming convention in .alert-actions
| EVAL alert_id = episode.id
// Join with .alert-actions to surface triage actions alongside evaluation data
| LOOKUP JOIN .alert-actions ON alert_id
| KEEP @timestamp, status, action_type, actor
| SORT @timestamp DESC
For most exploratory analysis, running separate queries filtered by group_hash is simpler and avoids the alert_id optionality issue.