Skip to content

Commit 9cc9de0

Browse files
authored
fix: time on filter (#5238)
1 parent 2ff52f5 commit 9cc9de0

File tree

4 files changed

+221
-5
lines changed

4 files changed

+221
-5
lines changed

keep/searchengine/searchengine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def search_alerts_by_cel(
106106
time_ago.astimezone(timezone.utc).replace(microsecond=0).isoformat()
107107
)
108108
cel_list = [
109-
f"lastReceived >= '{iso_utc_date}'",
109+
f"timestamp >= '{iso_utc_date}'",
110110
cel_query,
111111
]
112112
cel_query = " && ".join(f"({cel})" for cel in cel_list if cel)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "keep"
3-
version = "0.46.4"
3+
version = "0.46.5"
44
description = "Alerting. for developers, by developers."
55
authors = ["Keep Alerting LTD"]
66
packages = [{include = "keep"}]
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
"""
2+
Test for Keep Provider time_delta filtering bug.
3+
This test reproduces the issue described in https://github.com/keephq/keep/issues/5180
4+
"""
5+
6+
import datetime
7+
import pytest
8+
import time
9+
import uuid
10+
from datetime import timezone, timedelta
11+
from freezegun import freeze_time
12+
13+
from keep.api.core.dependencies import SINGLE_TENANT_UUID
14+
from keep.api.models.db.alert import Alert, LastAlert
15+
from keep.api.models.alert import AlertStatus
16+
from keep.api.utils.enrichment_helpers import convert_db_alerts_to_dto_alerts
17+
from keep.providers.keep_provider.keep_provider import KeepProvider
18+
from keep.contextmanager.contextmanager import ContextManager
19+
from keep.providers.models.provider_config import ProviderConfig
20+
21+
22+
def _create_valid_event(d, lastReceived=None):
23+
"""Helper function to create a valid event similar to conftest.py"""
24+
event = {
25+
"id": str(uuid.uuid4()),
26+
"name": "some-test-event",
27+
"status": "firing",
28+
"lastReceived": (
29+
str(lastReceived)
30+
if lastReceived
31+
else datetime.datetime.now(tz=timezone.utc).isoformat()
32+
),
33+
}
34+
event.update(d)
35+
return event
36+
37+
38+
def test_keep_provider_time_delta_filtering_bug(db_session):
39+
"""
40+
Test that reproduces the time_delta filtering bug.
41+
42+
This test verifies that when using Keep Provider with version 2 and time_delta,
43+
only alerts within the specified timeframe are returned, not all alerts.
44+
"""
45+
# Setup context
46+
tenant_id = SINGLE_TENANT_UUID
47+
context_manager = ContextManager(
48+
tenant_id=tenant_id,
49+
workflow_id=None
50+
)
51+
52+
# Create KeepProvider instance
53+
provider_config = ProviderConfig(authentication={})
54+
provider = KeepProvider(
55+
context_manager=context_manager,
56+
provider_id="test-keep",
57+
config=provider_config
58+
)
59+
60+
# Create alerts with different timestamps
61+
now = datetime.datetime.now(timezone.utc)
62+
old_time = now - timedelta(hours=2) # 2 hours ago
63+
recent_time = now - timedelta(seconds=30) # 30 seconds ago
64+
65+
# Create alert details similar to conftest.py setup_alerts
66+
alert_details = [
67+
{
68+
"source": ["test"],
69+
"status": AlertStatus.FIRING.value,
70+
"lastReceived": old_time.isoformat(),
71+
"fingerprint": "old-alert-fingerprint",
72+
"id": "old-alert"
73+
},
74+
{
75+
"source": ["test"],
76+
"status": AlertStatus.FIRING.value,
77+
"lastReceived": recent_time.isoformat(),
78+
"fingerprint": "recent-alert-fingerprint",
79+
"id": "recent-alert"
80+
}
81+
]
82+
83+
# Create Alert objects
84+
alerts = []
85+
for detail in alert_details:
86+
# Create timestamps from lastReceived
87+
timestamp = datetime.datetime.fromisoformat(detail["lastReceived"].replace('Z', '+00:00'))
88+
89+
alert = Alert(
90+
tenant_id=tenant_id,
91+
provider_type="test",
92+
provider_id="test",
93+
event=_create_valid_event(detail, detail["lastReceived"]),
94+
fingerprint=detail["fingerprint"],
95+
timestamp=timestamp
96+
)
97+
alerts.append(alert)
98+
99+
# Add alerts to database
100+
db_session.add_all(alerts)
101+
db_session.commit()
102+
103+
# Create LastAlert entries
104+
last_alerts = []
105+
for alert in alerts:
106+
last_alert = LastAlert(
107+
tenant_id=tenant_id,
108+
fingerprint=alert.fingerprint,
109+
timestamp=alert.timestamp,
110+
first_timestamp=alert.timestamp,
111+
alert_id=alert.id,
112+
)
113+
last_alerts.append(last_alert)
114+
115+
db_session.add_all(last_alerts)
116+
db_session.commit()
117+
118+
# Test with time_delta of approximately 1 minute (0.000694445 days)
119+
# This should only return the recent alert, not the old one
120+
time_delta_1_minute = 0.000694445
121+
122+
# Query using Keep Provider version 2 (which uses SearchEngine)
123+
with freeze_time(now):
124+
results = provider._query(
125+
version=2,
126+
filter="status == 'firing'",
127+
time_delta=time_delta_1_minute,
128+
limit=10000
129+
)
130+
131+
# This should fail because the bug causes all alerts to be returned
132+
# instead of just the ones within the time_delta
133+
assert len(results) == 1, f"Expected 1 alert within time_delta, but got {len(results)}"
134+
135+
# Verify it's the recent alert
136+
assert results[0].id == "recent-alert"
137+
138+
139+
def test_keep_provider_time_delta_filtering_version_1(db_session):
140+
"""
141+
Test that version 1 of Keep Provider correctly filters by time_delta.
142+
This should work correctly as it uses get_alerts_with_filters directly.
143+
"""
144+
# This test is simpler since we're testing version 1 which should work
145+
from keep.api.core.db import get_alerts_with_filters
146+
147+
tenant_id = SINGLE_TENANT_UUID
148+
149+
# Create alerts with different timestamps
150+
now = datetime.datetime.now(timezone.utc)
151+
old_time = now - timedelta(hours=2) # 2 hours ago
152+
recent_time = now - timedelta(seconds=30) # 30 seconds ago
153+
154+
# Create Alert objects directly
155+
alerts = []
156+
157+
old_alert = Alert(
158+
tenant_id=tenant_id,
159+
provider_type="test",
160+
provider_id="test",
161+
event=_create_valid_event({
162+
"id": "old-alert-v1",
163+
"status": AlertStatus.FIRING.value,
164+
"lastReceived": old_time.isoformat(),
165+
}),
166+
fingerprint="old-alert-v1-fingerprint",
167+
timestamp=old_time
168+
)
169+
170+
recent_alert = Alert(
171+
tenant_id=tenant_id,
172+
provider_type="test",
173+
provider_id="test",
174+
event=_create_valid_event({
175+
"id": "recent-alert-v1",
176+
"status": AlertStatus.FIRING.value,
177+
"lastReceived": recent_time.isoformat(),
178+
}),
179+
fingerprint="recent-alert-v1-fingerprint",
180+
timestamp=recent_time
181+
)
182+
183+
alerts = [old_alert, recent_alert]
184+
185+
# Add alerts to database
186+
db_session.add_all(alerts)
187+
db_session.commit()
188+
189+
# Create LastAlert entries (required by get_alerts_with_filters)
190+
last_alerts = []
191+
for alert in alerts:
192+
last_alert = LastAlert(
193+
tenant_id=tenant_id,
194+
fingerprint=alert.fingerprint,
195+
timestamp=alert.timestamp,
196+
first_timestamp=alert.timestamp,
197+
alert_id=alert.id,
198+
)
199+
last_alerts.append(last_alert)
200+
201+
db_session.add_all(last_alerts)
202+
db_session.commit()
203+
204+
# Test using get_alerts_with_filters directly (version 1 approach)
205+
time_delta_1_minute = 0.000694445
206+
207+
with freeze_time(now):
208+
filtered_alerts = get_alerts_with_filters(
209+
tenant_id=tenant_id,
210+
filters=None, # Test just time_delta filtering without additional filters
211+
time_delta=time_delta_1_minute
212+
)
213+
214+
# This should work correctly - only return recent alert
215+
assert len(filtered_alerts) == 1, f"Expected 1 alert within time_delta, but got {len(filtered_alerts)}"
216+
assert filtered_alerts[0].event["id"] == "recent-alert-v1"

tests/test_search_alerts.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,20 +1450,20 @@ def test_alerts_enrichment_in_search(db_session, client, test_app, elastic_clien
14501450
@pytest.mark.parametrize(
14511451
"cel_query, timeframe, limit, expected_cel",
14521452
[
1453-
(None, 0.1667, 223, "(lastReceived >= '2025-06-18T11:51:20+00:00')"),
1453+
(None, 0.1667, 223, "(timestamp >= '2025-06-18T11:51:20+00:00')"),
14541454
(
14551455
"providerType != 'gcp'",
14561456
0.1667,
14571457
500,
1458-
"(lastReceived >= '2025-06-18T11:51:20+00:00') && (providerType != 'gcp')",
1458+
"(timestamp >= '2025-06-18T11:51:20+00:00') && (providerType != 'gcp')",
14591459
),
14601460
("providerType != 'gcp'", None, 2, "providerType != 'gcp'"),
14611461
(" providerType != 'gcp' ", None, 2, "providerType != 'gcp'"),
14621462
(
14631463
"name.contains('CPU')",
14641464
0.5,
14651465
2,
1466-
"(lastReceived >= '2025-06-18T03:51:23+00:00') && (name.contains('CPU'))",
1466+
"(timestamp >= '2025-06-18T03:51:23+00:00') && (name.contains('CPU'))",
14671467
),
14681468
],
14691469
)

0 commit comments

Comments
 (0)