-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
feat(aci): Only update open periods if the initial one exists #90079
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
Merged
Merged
Changes from 2 commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 |
---|---|---|
|
@@ -21,7 +21,7 @@ | |
from django.utils.translation import gettext_lazy as _ | ||
from snuba_sdk import Column, Condition, Op | ||
|
||
from sentry import eventstore, eventtypes, features, options, tagstore | ||
from sentry import eventstore, eventtypes, options, tagstore | ||
from sentry.backup.scopes import RelocationScope | ||
from sentry.constants import DEFAULT_LOGGER_NAME, LOG_LEVELS, MAX_CULPRIT_LENGTH | ||
from sentry.db.models import ( | ||
|
@@ -42,10 +42,8 @@ | |
PriorityChangeReason, | ||
get_priority_for_ongoing_group, | ||
) | ||
from sentry.models.activity import Activity | ||
from sentry.models.commit import Commit | ||
from sentry.models.grouphistory import record_group_history, record_group_history_from_activity_type | ||
from sentry.models.groupopenperiod import GroupOpenPeriod | ||
from sentry.models.organization import Organization | ||
from sentry.snuba.dataset import Dataset | ||
from sentry.snuba.referrer import Referrer | ||
|
@@ -62,7 +60,6 @@ | |
from sentry.utils.strings import strip, truncatechars | ||
|
||
if TYPE_CHECKING: | ||
from sentry.incidents.utils.metric_issue_poc import OpenPeriod | ||
from sentry.integrations.services.integration import RpcIntegration | ||
from sentry.models.environment import Environment | ||
from sentry.models.team import Team | ||
|
@@ -448,6 +445,7 @@ def update_group_status( | |
) -> None: | ||
"""For each groups, update status to `status` and create an Activity.""" | ||
from sentry.models.activity import Activity | ||
from sentry.models.groupopenperiod import update_group_open_period | ||
|
||
modified_groups_list = [] | ||
selected_groups = Group.objects.filter(id__in=[g.id for g in groups]).exclude( | ||
|
@@ -1063,185 +1061,3 @@ def pre_save_group_default_substatus(instance, sender, *args, **kwargs): | |
"No substatus allowed for group", | ||
extra={"status": instance.status, "substatus": instance.substatus}, | ||
) | ||
|
||
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. all of this is moved to |
||
|
||
def get_last_checked_for_open_period(group: Group) -> datetime: | ||
from sentry.incidents.models.alert_rule import AlertRule | ||
from sentry.issues.grouptype import MetricIssuePOC | ||
|
||
event = group.get_latest_event() | ||
last_checked = group.last_seen | ||
if event and group.type == MetricIssuePOC.type_id: | ||
alert_rule_id = event.data.get("contexts", {}).get("metric_alert", {}).get("alert_rule_id") | ||
if alert_rule_id: | ||
try: | ||
alert_rule = AlertRule.objects.get(id=alert_rule_id) | ||
now = timezone.now() | ||
last_checked = now - timedelta(seconds=alert_rule.snuba_query.time_window) | ||
except AlertRule.DoesNotExist: | ||
pass | ||
|
||
return last_checked | ||
|
||
|
||
def get_open_periods_for_group( | ||
group: Group, | ||
query_start: datetime | None = None, | ||
query_end: datetime | None = None, | ||
offset: int | None = None, | ||
limit: int | None = None, | ||
) -> list[OpenPeriod]: | ||
from sentry.incidents.utils.metric_issue_poc import OpenPeriod | ||
from sentry.models.groupopenperiod import GroupOpenPeriod | ||
|
||
if not features.has("organizations:issue-open-periods", group.organization): | ||
return [] | ||
|
||
# Try to get open periods from the GroupOpenPeriod table first | ||
group_open_periods = GroupOpenPeriod.objects.filter(group=group) | ||
if group_open_periods.exists() and query_start: | ||
group_open_periods = group_open_periods.filter( | ||
date_started__gte=query_start, date_ended__lte=query_end, id__gte=offset or 0 | ||
).order_by("-date_started")[:limit] | ||
|
||
return [ | ||
OpenPeriod( | ||
start=period.date_started, | ||
end=period.date_ended, | ||
duration=period.date_ended - period.date_started if period.date_ended else None, | ||
is_open=period.date_ended is None, | ||
last_checked=get_last_checked_for_open_period(group), | ||
) | ||
for period in group_open_periods | ||
] | ||
|
||
# If there are no open periods in the table, we need to calculate them | ||
# from the activity log. | ||
# TODO(snigdha): This is temporary until we have backfilled the GroupOpenPeriod table | ||
|
||
if query_start is None or query_end is None: | ||
query_start = timezone.now() - timedelta(days=90) | ||
query_end = timezone.now() | ||
|
||
query_limit = limit * 2 if limit else None | ||
# Filter to REGRESSION and RESOLVED activties to find the bounds of each open period. | ||
# The only UNRESOLVED activity we would care about is the first UNRESOLVED activity for the group creation, | ||
# but we don't create an entry for that . | ||
activities = Activity.objects.filter( | ||
group=group, | ||
type__in=[ActivityType.SET_REGRESSION.value, ActivityType.SET_RESOLVED.value], | ||
datetime__gte=query_start, | ||
datetime__lte=query_end, | ||
).order_by("-datetime")[:query_limit] | ||
|
||
open_periods = [] | ||
start: datetime | None = None | ||
end: datetime | None = None | ||
last_checked = get_last_checked_for_open_period(group) | ||
|
||
# Handle currently open period | ||
if group.status == GroupStatus.UNRESOLVED and len(activities) > 0: | ||
open_periods.append( | ||
OpenPeriod( | ||
start=activities[0].datetime, | ||
end=None, | ||
duration=None, | ||
is_open=True, | ||
last_checked=last_checked, | ||
) | ||
) | ||
activities = activities[1:] | ||
|
||
for activity in activities: | ||
if activity.type == ActivityType.SET_RESOLVED.value: | ||
end = activity.datetime | ||
elif activity.type == ActivityType.SET_REGRESSION.value: | ||
start = activity.datetime | ||
if end is not None: | ||
open_periods.append( | ||
OpenPeriod( | ||
start=start, | ||
end=end, | ||
duration=end - start, | ||
is_open=False, | ||
last_checked=end, | ||
) | ||
) | ||
end = None | ||
|
||
# Add the very first open period, which has no UNRESOLVED activity for the group creation | ||
open_periods.append( | ||
OpenPeriod( | ||
start=group.first_seen, | ||
end=end if end else None, | ||
duration=end - group.first_seen if end else None, | ||
is_open=False if end else True, | ||
last_checked=end if end else last_checked, | ||
) | ||
) | ||
|
||
if offset and limit: | ||
return open_periods[offset : offset + limit] | ||
|
||
if limit: | ||
return open_periods[:limit] | ||
|
||
return open_periods | ||
|
||
|
||
def update_group_open_period( | ||
group: Group, | ||
new_status: int, | ||
activity: Activity | None, | ||
should_reopen_open_period: bool, | ||
) -> None: | ||
""" | ||
Update an existing open period when the group is resolved or unresolved. | ||
|
||
On resolution, we set the date_ended to the resolution time and link the activity to the open period. | ||
On unresolved, we clear the date_ended and resolution_activity fields. This is only done if the group | ||
is unresolved manually without a regression. If the group is unresolved due to a regression, the | ||
open periods will be updated during ingestion. | ||
""" | ||
if not features.has("organizations:issue-open-periods", group.project.organization): | ||
return | ||
|
||
if new_status not in (GroupStatus.RESOLVED, GroupStatus.UNRESOLVED): | ||
return | ||
|
||
find_open = new_status != GroupStatus.UNRESOLVED | ||
open_period = ( | ||
GroupOpenPeriod.objects.filter(group=group, date_ended__isnull=find_open) | ||
.order_by("-date_started") | ||
.first() | ||
) | ||
if not open_period: | ||
logger.error( | ||
"Unable to update open period, no open period found", | ||
extra={"group_id": group.id}, | ||
) | ||
return | ||
|
||
if new_status == GroupStatus.RESOLVED: | ||
if activity is None: | ||
logger.warning( | ||
"Missing activity for group resolution, querying for it", | ||
extra={"group_id": group.id}, | ||
) | ||
activity = ( | ||
Activity.objects.filter( | ||
group=group, | ||
type=ActivityType.SET_RESOLVED.value, | ||
) | ||
.order_by("-datetime") | ||
.first() | ||
) | ||
|
||
end_time = group.resolved_at or (activity.datetime if activity else None) | ||
open_period.update( | ||
date_ended=end_time, | ||
resolution_activity=activity, | ||
user_id=activity.user_id if activity else None, | ||
) | ||
elif new_status == GroupStatus.UNRESOLVED and should_reopen_open_period: | ||
open_period.update(date_ended=None, resolution_activity=None, user_id=None) |
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.
Sorry for the larger diff - calling out the logic changes