Skip to content
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

v1.8.6 #4729

Merged
merged 24 commits into from
Jul 24, 2024
Merged

v1.8.6 #4729

Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0325e2a
Label error handling tweaks (#4687)
teodosii Jul 17, 2024
65a794a
PR template: "Closes" -> "Related to" (#4693)
vstpme Jul 17, 2024
49d1127
Fix `send_bundled_notification` task (#4696)
Ferril Jul 17, 2024
4e03732
Drop stale metrics cache (#4695)
Ferril Jul 17, 2024
dd84aa1
Add missing app_type argument to uninstall request (#4700)
Konstantinov-Innokentii Jul 18, 2024
7777a60
Handle multiple unpage event logs when getting paged users (#4698)
matiasb Jul 18, 2024
1c33964
Update schedule events internal API to return default priority level …
matiasb Jul 18, 2024
8d82b07
Cleanup and split tiltfile by profiles (#4691)
brojd Jul 19, 2024
1465db3
Support stack header from chatops-proxy (#4578)
Konstantinov-Innokentii Jul 19, 2024
87d7982
Unified Slack app reinstall (#4682)
vstpme Jul 19, 2024
1491e28
Update regex to jinja route conversion to correctly escape double quo…
matiasb Jul 19, 2024
ab700fd
Update OrderedModel serializer not to save previously updated order (…
matiasb Jul 19, 2024
ec360f0
Fix bug for incorrect arguments used to invoke cleanup_empty_deleted_…
mderynck Jul 19, 2024
696aca2
Make it clear alert groups can't be searched (#4713)
vstpme Jul 22, 2024
1c88107
Fix slack app redirect (#4714)
Konstantinov-Innokentii Jul 22, 2024
4438443
hide forbidden actions from Add menu in schedule (#4708)
brojd Jul 22, 2024
a558703
AG filters (#4718)
teodosii Jul 23, 2024
a53f207
Alert group search feature flags (#4579)
vstpme Jul 23, 2024
ed57c30
Prepare for changes to OnCall plugin initialization (#4715)
mderynck Jul 23, 2024
53556bd
show chatops proxy error (#4719)
brojd Jul 24, 2024
4877b9d
Set unique key on each rendered route (#4721)
teodosii Jul 24, 2024
94219c2
Introduce slash command matcher (#4717)
Konstantinov-Innokentii Jul 24, 2024
9da5b94
Fix app redirect (#4725)
Konstantinov-Innokentii Jul 24, 2024
b27a158
Fix `/escalate` predefined org (#4723)
vstpme Jul 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Alert group search feature flags (#4579)
# What this PR does

Adds two feature flags to experiment with the alert group search feature
(on top of the existing `FEATURE_ALERT_GROUP_SEARCH_ENABLED` flag):
* `FEATURE_ALERT_GROUP_SEARCH_CUTOFF_DAYS` - search window size in days
* `ALERT_GROUPS_DISABLE_PREFER_ORDERING_INDEX` - enable a workaround
that effectively forces the `alert_group_list_index` index to be used
for alert group list requests

## Which issue(s) this PR closes

Related to grafana/oncall-private#2679

<!--
*Note*: if you have more than one GitHub issue that this PR closes, be
sure to preface
each issue link with a [closing
keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue).
This ensures that the issue(s) are auto-closed once the PR has been
merged.
-->

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.
  • Loading branch information
vstpme authored Jul 23, 2024
commit a53f20735522d5457121660c1ea7cf613d62f55c
57 changes: 57 additions & 0 deletions engine/apps/api/tests/test_alert_group.py
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@
from apps.api.serializers.alert import AlertFieldsCacheSerializerMixin
from apps.api.serializers.alert_group import AlertGroupFieldsCacheSerializerMixin
from apps.base.models import UserNotificationPolicyLogRecord
from common.api_helpers.filters import DateRangeFilterMixin

alert_raw_request_data = {
"evalMatches": [
@@ -975,6 +976,62 @@ def test_get_filter_labels(
assert response.json()["results"][0]["pk"] == alert_groups[0].public_primary_key


@pytest.mark.django_db
def test_get_title_search(
settings,
make_organization_and_user_with_plugin_token,
make_alert_receive_channel,
make_channel_filter,
make_alert_group,
make_alert,
make_user_auth_headers,
):
settings.FEATURE_ALERT_GROUP_SEARCH_ENABLED = True
settings.FEATURE_ALERT_GROUP_SEARCH_CUTOFF_DAYS = 30
organization, user, token = make_organization_and_user_with_plugin_token()

alert_receive_channel = make_alert_receive_channel(organization)
channel_filter = make_channel_filter(alert_receive_channel, is_default=True)

alert_groups = []
for i in range(3):
alert_group = make_alert_group(
alert_receive_channel, channel_filter=channel_filter, web_title_cache=f"testing {i+1}"
)
# alert groups starting every months going back
alert_group.started_at = timezone.now() - datetime.timedelta(days=10 + 30 * i)
alert_group.save(update_fields=["started_at"])
make_alert(alert_group=alert_group, raw_request_data=alert_raw_request_data)
alert_groups.append(alert_group)

client = APIClient()
url = reverse("api-internal:alertgroup-list")

# check that the search returns alert groups started in the last 30 days
response = client.get(
url + "?search=testing",
format="json",
**make_user_auth_headers(user, token),
)
assert response.status_code == status.HTTP_200_OK
assert len(response.json()["results"]) == 1
assert response.json()["results"][0]["pk"] == alert_groups[0].public_primary_key

# check that the search returns alert groups started in the last 30 days of specified date range
response = client.get(
url
+ "?search=testing&started_at={}_{}".format(
(timezone.now() - datetime.timedelta(days=500)).strftime(DateRangeFilterMixin.DATE_FORMAT),
(timezone.now() - datetime.timedelta(days=30)).strftime(DateRangeFilterMixin.DATE_FORMAT),
),
format="json",
**make_user_auth_headers(user, token),
)
assert response.status_code == status.HTTP_200_OK
assert len(response.json()["results"]) == 1
assert response.json()["results"][0]["pk"] == alert_groups[1].public_primary_key


@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
49 changes: 42 additions & 7 deletions engine/apps/api/views/alert_group.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import typing
from datetime import timedelta

from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Count, Max, Q
from django.utils import timezone
from django_filters import rest_framework as filters
from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework import mixins, serializers, status, viewsets
@@ -228,6 +230,30 @@ def retrieve(self, request, *args, **kwargs):
return Response(data={"error_code": "wrong_team"}, status=status.HTTP_403_FORBIDDEN)


class AlertGroupSearchFilter(SearchFilter):
def filter_queryset(self, request, queryset, view):
search_fields = self.get_search_fields(view, request)
search_terms = self.get_search_terms(request)
if not search_fields or not search_terms:
return queryset

if settings.FEATURE_ALERT_GROUP_SEARCH_CUTOFF_DAYS:
started_at = request.query_params.get("started_at")
end = DateRangeFilterMixin.parse_custom_datetime_range(started_at)[1] if started_at else timezone.now()
queryset = queryset.filter(
started_at__gte=end - timedelta(days=settings.FEATURE_ALERT_GROUP_SEARCH_CUTOFF_DAYS)
)

return super().filter_queryset(request, queryset, view)

def get_search_fields(self, view, request):
return (
["=public_primary_key", "=inside_organization_number", "web_title_cache"]
if settings.FEATURE_ALERT_GROUP_SEARCH_ENABLED
else []
)


class AlertGroupView(
PreviewTemplateMixin,
AlertGroupTeamFilteringMixin,
@@ -275,12 +301,7 @@ class AlertGroupView(

pagination_class = AlertGroupCursorPaginator

filter_backends = [SearchFilter, filters.DjangoFilterBackend]
search_fields = (
["=public_primary_key", "=inside_organization_number", "web_title_cache"]
if settings.FEATURE_ALERT_GROUP_SEARCH_ENABLED
else []
)
filter_backends = [AlertGroupSearchFilter, filters.DjangoFilterBackend]
filterset_class = AlertGroupFilter

def get_serializer_class(self):
@@ -311,6 +332,13 @@ def get_queryset(self, ignore_filtering_by_available_teams=False):
alert_receive_channels_ids = list(alert_receive_channels_qs.values_list("id", flat=True))
queryset = AlertGroup.objects.filter(channel__in=alert_receive_channels_ids)

if settings.ALERT_GROUPS_DISABLE_PREFER_ORDERING_INDEX:
# workaround related to MySQL "ORDER BY LIMIT Query Optimizer Bug"
# read more: https://hackmysql.com/infamous-order-by-limit-query-optimizer-bug/
# this achieves the same effect as "FORCE INDEX (alert_group_list_index)" when
# paired with "ORDER BY started_at_optimized DESC" (ordering is performed in AlertGroupCursorPaginator).
queryset = queryset.extra({"started_at_optimized": "alerts_alertgroup.started_at + 0"})

# Filter by labels. Since alert group labels are "static" filter by names, not IDs.
label_query = self.request.query_params.getlist("label", [])
kv_pairs = parse_label_query(label_query)
@@ -811,7 +839,14 @@ def filters(self, request):
]

if settings.FEATURE_ALERT_GROUP_SEARCH_ENABLED:
filter_options = [{"name": "search", "type": "search"}] + filter_options
description = "Search by alert group ID, number or title."
if settings.FEATURE_ALERT_GROUP_SEARCH_CUTOFF_DAYS:
description += (
f" The search is limited to alert groups started in the last "
f"{settings.FEATURE_ALERT_GROUP_SEARCH_CUTOFF_DAYS} days of the specified date range."
)

filter_options = [{"name": "search", "type": "search", "description": description}] + filter_options

if is_labels_feature_enabled(self.request.auth.organization):
filter_options.append(
3 changes: 2 additions & 1 deletion engine/common/api_helpers/paginators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import typing

from django.conf import settings
from rest_framework.pagination import BasePagination, CursorPagination, PageNumberPagination
from rest_framework.response import Response

@@ -85,4 +86,4 @@ class FifteenPageSizePaginator(PathPrefixedPagePagination):

class AlertGroupCursorPaginator(PathPrefixedCursorPagination):
page_size = 25
ordering = "-started_at" # ordering by "-started_at", so it uses the right index (see AlertGroup.Meta.indexes)
ordering = "-started_at_optimized" if settings.ALERT_GROUPS_DISABLE_PREFER_ORDERING_INDEX else "-started_at"
2 changes: 1 addition & 1 deletion engine/common/utils.py
Original file line number Diff line number Diff line change
@@ -129,7 +129,7 @@ def getenv_boolean(variable_name: str, default: bool) -> bool:
return value.lower() in ("true", "1")


def getenv_integer(variable_name: str, default: int) -> int:
def getenv_integer(variable_name: str, default: int | None) -> int | None:
value = os.environ.get(variable_name)
if value is None:
return default
5 changes: 5 additions & 0 deletions engine/settings/base.py
Original file line number Diff line number Diff line change
@@ -72,6 +72,7 @@
# Enable labels feature for organizations from the list. Use OnCall organization ID, for this flag
FEATURE_LABELS_ENABLED_PER_ORG = getenv_list("FEATURE_LABELS_ENABLED_PER_ORG", default=list())
FEATURE_ALERT_GROUP_SEARCH_ENABLED = getenv_boolean("FEATURE_ALERT_GROUP_SEARCH_ENABLED", default=False)
FEATURE_ALERT_GROUP_SEARCH_CUTOFF_DAYS = getenv_integer("FEATURE_ALERT_GROUP_SEARCH_CUTOFF_DAYS", default=None)
FEATURE_NOTIFICATION_BUNDLE_ENABLED = getenv_boolean("FEATURE_NOTIFICATION_BUNDLE_ENABLED", default=False)

TWILIO_API_KEY_SID = os.environ.get("TWILIO_API_KEY_SID")
@@ -188,6 +189,10 @@ class DatabaseTypes:

pymysql.install_as_MySQLdb()

ALERT_GROUPS_DISABLE_PREFER_ORDERING_INDEX = DATABASE_TYPE == DatabaseTypes.MYSQL and getenv_boolean(
"ALERT_GROUPS_DISABLE_PREFER_ORDERING_INDEX", default=False
)

# Redis
REDIS_USERNAME = os.getenv("REDIS_USERNAME", "")
REDIS_PASSWORD = os.getenv("REDIS_PASSWORD")
Original file line number Diff line number Diff line change
@@ -248,13 +248,13 @@ class _RemoteFilters extends Component<RemoteFiltersProps, RemoteFiltersState> {
};

handleSearch = (query: string) => {
const { filters } = this.state;
const { filters, filterOptions } = this.state;

const searchFilter = filters.find((filter: FilterOption) => filter.name === 'search');

const newFilters = filters;
if (!searchFilter) {
newFilters.push({ name: 'search', type: 'search' });
newFilters.push(filterOptions.find((filter: FilterOption) => filter.name === 'search'));
} else {
this.searchRef.current.focus();
}
Loading