-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(alerts): add a regular job to detect anomalies #22762
Merged
+356
−35
Merged
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
eb4cb49
init
nikitaevg ba51409
initial version of the regular job
nikitaevg bb71f5a
small polishing
nikitaevg 1d57843
Merge remote-tracking branch 'upstream/master' into 14331-regular-job
nikitaevg 15e0b6b
small test fix
nikitaevg 71c44d9
fix types in tests
nikitaevg 5e59a5d
fix the crontab schedule to every hour
nikitaevg 29e613b
add a newline to the template
nikitaevg b1a0219
add a test to check insight date range
nikitaevg 0b2e57a
Merge remote-tracking branch 'upstream/master' into 14331-regular-job
nikitaevg 29d9305
use the new display type naming
nikitaevg 0cd1a01
Merge remote-tracking branch 'upstream/master' into 14331-regular-job
nikitaevg b8b4fec
address PR comments
nikitaevg 3475c37
Merge branch 'master' into 14331-regular-job
webjunkie 16227b9
Refactor things
webjunkie 712d780
Fix scheduled task setup
webjunkie f179e37
Refactor more
webjunkie 5cf4e46
Fix group setup
webjunkie d940441
address comments
nikitaevg d91bcc9
Merge remote-tracking branch 'upstream/master' into 14331-regular-job
nikitaevg ed27736
fix typing
nikitaevg a1966b4
Merge remote-tracking branch 'upstream/master' into 14331-regular-job
nikitaevg 4e97160
Revert "Fix scheduled task setup"
webjunkie 028d155
use si for chains
nikitaevg 1920482
Merge remote-tracking branch 'upstream/master' into 14331-regular-job
nikitaevg fa7dc4c
use timestamp for the campaign key
nikitaevg e5b44ca
Merge branch 'master' into 14331-regular-job
nikitaevg b8d4e60
Merge branch 'master' into 14331-regular-job
nikitaevg e3db36a
Merge branch 'master' into 14331-regular-job
webjunkie 4183314
Merge branch 'master' into 14331-regular-job
webjunkie 66f0c4a
brush up the PR
nikitaevg d274bea
Merge branch 'master' into 14331-regular-job
nikitaevg 0e8d28a
Merge branch 'master' into 14331-regular-job
webjunkie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -268,7 +268,7 @@ export const insightNavLogic = kea<insightNavLogicType>([ | |
}, | ||
})), | ||
urlToAction(({ actions }) => ({ | ||
'/insights/:shortId(/:mode)(/:subscriptionId)': ( | ||
'/insights/:shortId(/:mode)(/:itemId)': ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As per #22554 (comment) |
||
_, // url params | ||
{ dashboard, ...searchParams }, // search params | ||
{ filters: _filters } // hash params | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
nikitaevg marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
from celery import shared_task | ||
from celery.canvas import group, chain | ||
from django.utils import timezone | ||
import math | ||
import structlog | ||
|
||
from posthog.api.services.query import ExecutionMode | ||
from posthog.caching.calculate_results import calculate_for_query_based_insight | ||
from posthog.email import EmailMessage | ||
from posthog.hogql_queries.legacy_compatibility.flagged_conversion_manager import ( | ||
conversion_to_query_based, | ||
) | ||
from posthog.models import Alert | ||
from posthog.schema import AnomalyCondition | ||
|
||
logger = structlog.get_logger(__name__) | ||
|
||
|
||
def check_all_alerts() -> None: | ||
alert_ids = list(Alert.objects.all().values_list("id", flat=True)) | ||
|
||
group_count = 10 | ||
# All groups but the last one will have a group_size size. | ||
# The last group will have at most group_size size. | ||
group_size = int(math.ceil(len(alert_ids) / group_count)) | ||
|
||
groups = [] | ||
for i in range(0, len(alert_ids), group_size): | ||
alert_id_group = alert_ids[i : i + group_size] | ||
chained_calls = chain([check_alert_task.si(alert_id) for alert_id in alert_id_group]) | ||
groups.append(chained_calls) | ||
|
||
group(groups).apply_async() | ||
|
||
|
||
def check_alert(alert_id: int) -> None: | ||
alert = Alert.objects.get(pk=alert_id) | ||
insight = alert.insight | ||
|
||
with conversion_to_query_based(insight): | ||
calculation_result = calculate_for_query_based_insight( | ||
insight, | ||
execution_mode=ExecutionMode.RECENT_CACHE_CALCULATE_BLOCKING_IF_STALE, | ||
user=None, | ||
) | ||
|
||
if not calculation_result.result: | ||
raise RuntimeError(f"No results for alert {alert.id}") | ||
|
||
anomaly_condition = AnomalyCondition.model_validate(alert.anomaly_condition) | ||
thresholds = anomaly_condition.absoluteThreshold | ||
|
||
result = calculation_result.result[0] | ||
aggregated_value = result["aggregated_value"] | ||
anomalies_descriptions = [] | ||
|
||
if thresholds.lower is not None and aggregated_value < thresholds.lower: | ||
anomalies_descriptions += [ | ||
f"The trend value ({aggregated_value}) is below the lower threshold ({thresholds.lower})" | ||
] | ||
if thresholds.upper is not None and aggregated_value > thresholds.upper: | ||
anomalies_descriptions += [ | ||
f"The trend value ({aggregated_value}) is above the upper threshold ({thresholds.upper})" | ||
] | ||
|
||
if not anomalies_descriptions: | ||
logger.info("No threshold met", alert_id=alert.id) | ||
return | ||
|
||
send_notifications(alert, anomalies_descriptions) | ||
|
||
|
||
@shared_task(ignore_result=True) | ||
def check_all_alerts_task() -> None: | ||
check_all_alerts() | ||
|
||
|
||
@shared_task(ignore_result=True) | ||
def check_alert_task(alert_id: int) -> None: | ||
check_alert(alert_id) | ||
|
||
|
||
def send_notifications(alert: Alert, anomalies_descriptions: list[str]) -> None: | ||
subject = f"PostHog alert {alert.name} has anomalies" | ||
campaign_key = f"alert-anomaly-notification-{alert.id}-{timezone.now().timestamp()}" | ||
insight_url = f"/project/{alert.team.pk}/insights/{alert.insight.short_id}" | ||
alert_url = f"{insight_url}/alerts/{alert.id}" | ||
message = EmailMessage( | ||
campaign_key=campaign_key, | ||
subject=subject, | ||
template_name="alert_anomaly", | ||
template_context={ | ||
"anomalies_descriptions": anomalies_descriptions, | ||
"insight_url": insight_url, | ||
"insight_name": alert.insight.name, | ||
"alert_url": alert_url, | ||
"alert_name": alert.name, | ||
}, | ||
) | ||
targets = list(filter(len, alert.target_value.split(","))) | ||
if not targets: | ||
raise RuntimeError(f"no targets configured for the alert {alert.id}") | ||
for target in targets: | ||
message.add_recipient(email=target) | ||
|
||
logger.info(f"Send notifications about {len(anomalies_descriptions)} anomalies", alert_id=alert.id) | ||
message.send() |
Empty file.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a redundant field