Skip to content

Commit b94e099

Browse files
authored
Merge pull request #248 from grafana/dev
Merge dev to main
2 parents 52ae155 + 548b055 commit b94e099

File tree

84 files changed

+812
-836
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+812
-836
lines changed

CHANGELOG.md

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
# Change Log
22

3-
## v1.0.5 (2022-07-12)
4-
3+
## v1.0.7 (2022-07-18)
4+
- Backend & frontend bug fixes
5+
- Deployment improvements
6+
- Reshape webhook payload for outgoing webhooks
7+
- Add escalation chain usage info on escalation chains page
8+
- Improve alert group list load speeds and simplify caching system
9+
10+
## v1.0.6 (2022-07-12)
511
- Manual Incidents enabled for teams
612
- Fix phone notifications for OSS
713
- Public API improvements
814

15+
## v1.0.5 (2022-07-06)
16+
- Bump Django to 3.2.14
17+
- Fix PagerDuty iCal parsing
18+
919
## 1.0.4 (2022-06-28)
1020
- Allow Telegram DMs without channel connection.
1121

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ Grafana Url: http://grafana:3000
5656
6. Enjoy! Check our [OSS docs](https://grafana.com/docs/grafana-cloud/oncall/open-source/) if you want to set up Slack, Telegram, Twilio or SMS/calls through Grafana Cloud.
5757

5858

59+
## Update version
60+
To update your Grafana OnCall hobby environment:
61+
62+
```shell
63+
# Update Docker images
64+
docker-compose --env-file .env_hobby -f docker-compose.yml pull engine celery oncall_db_migration
65+
66+
# Re-deploy
67+
docker-compose --env-file .env_hobby -f docker-compose.yml up -d --remove-orphans
68+
```
69+
70+
After updating the engine, you'll also need to click the "Update" button on the [plugin version page](http://localhost:3000/plugins/grafana-oncall-app?page=version-history).
71+
See [Grafana docs](https://grafana.com/docs/grafana/latest/administration/plugin-management/#update-a-plugin) for more info on updating Grafana plugins.
72+
5973
## Join community
6074

6175
<a href="https://github.com/grafana/oncall/discussions/categories/community-calls"><img width="200px" src="docs/img/community_call.png"></a>

docs/sources/integrations/configure-outgoing-webhooks.md

+1-3
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,8 @@ To automatically send alert data to a destination URL via outgoing webhook:
3232

3333
The format you use to call the variables must match the structure of how the fields are nested in the alert payload. The **Data** field can use the following four variables to auto-populate the webhook payload with information about the first alert in the alert group:
3434

35-
- `{{ alert_title }}`
36-
- `{{ alert_message }}`
37-
- `{{ alert_url }}`
3835
- `{{ alert_payload }}`
36+
- `{{ alert_group_id }}`
3937
<br>
4038

4139
`alert_payload` is always the first level of any variable you want to call.

docs/sources/open-source.md

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ Check the [helm chart](https://github.com/grafana/oncall/tree/dev/helm/oncall) f
2929

3030
We'll always be happy to provide assistance with production deployment in [our communities](https://github.com/grafana/oncall#join-community)!
3131

32+
## Update Grafana OnCall OSS
33+
To update an OSS installation of Grafana OnCall, please see the update docs:
34+
- **Hobby** playground environment: [README.md](https://github.com/grafana/oncall#update)
35+
- **Production** Helm environment: [Helm update](https://github.com/grafana/oncall/tree/dev/helm/oncall#update)
36+
3237
## Slack Setup
3338

3439
The Slack integration for Grafana OnCall leverages Slack API features to provide a customizable and useful integration. Refer to the following steps to configure the Slack integration:

engine/apps/alerts/incident_appearance/renderers/base_renderer.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ def templater_class(self):
1818

1919

2020
class AlertGroupBaseRenderer(ABC):
21-
def __init__(self, alert_group):
21+
def __init__(self, alert_group, alert=None):
22+
if alert is None:
23+
alert = alert_group.alerts.first()
24+
2225
self.alert_group = alert_group
23-
self.alert_renderer = self.alert_renderer_class(self.alert_group.alerts.first())
26+
self.alert_renderer = self.alert_renderer_class(alert)
2427

2528
@property
2629
@abstractmethod

engine/apps/alerts/incident_appearance/renderers/web_renderer.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ def render(self):
2020

2121

2222
class AlertGroupWebRenderer(AlertGroupBaseRenderer):
23-
def __init__(self, alert_group):
24-
super().__init__(alert_group)
23+
def __init__(self, alert_group, alert=None):
24+
if alert is None:
25+
alert = alert_group.alerts.last()
2526

26-
# use the last alert to render content
27-
self.alert_renderer = self.alert_renderer_class(self.alert_group.alerts.last())
27+
super().__init__(alert_group, alert)
2828

2929
@property
3030
def alert_renderer_class(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 3.2.13 on 2022-07-11 11:06
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('alerts', '0003_grafanaalertingcontactpoint_datasource_uid'),
10+
]
11+
12+
operations = [
13+
migrations.RemoveField(
14+
model_name='alertgroup',
15+
name='active_cache_for_web_calculation_id',
16+
),
17+
migrations.RemoveField(
18+
model_name='alertgroup',
19+
name='cached_render_for_web',
20+
),
21+
]

engine/apps/alerts/models/alert.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from django.apps import apps
66
from django.conf import settings
77
from django.core.validators import MinLengthValidator
8-
from django.db import models, transaction
8+
from django.db import models
99
from django.db.models import JSONField
1010
from django.db.models.signals import post_save
1111

@@ -261,9 +261,6 @@ def listen_for_alert_model_save(sender, instance, created, *args, **kwargs):
261261
else:
262262
distribute_alert.apply_async((instance.pk,), countdown=TASK_DELAY_SECONDS)
263263

264-
logger.info(f"Recalculate AG cache. Reason: save alert model {instance.pk}")
265-
transaction.on_commit(instance.group.schedule_cache_for_web)
266-
267264

268265
# Connect signal to base Alert class
269266
post_save.connect(listen_for_alert_model_save, Alert)

engine/apps/alerts/models/alert_group.py

+5-81
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,9 @@
88
from celery import uuid as celery_uuid
99
from django.apps import apps
1010
from django.conf import settings
11-
from django.core.cache import cache
1211
from django.core.validators import MinLengthValidator
13-
from django.db import IntegrityError, models, transaction
12+
from django.db import IntegrityError, models
1413
from django.db.models import JSONField, Q, QuerySet
15-
from django.db.models.signals import post_save
16-
from django.dispatch import receiver
1714
from django.utils import timezone
1815
from django.utils.functional import cached_property
1916

@@ -22,16 +19,9 @@
2219
from apps.alerts.incident_appearance.renderers.slack_renderer import AlertGroupSlackRenderer
2320
from apps.alerts.incident_log_builder import IncidentLogBuilder
2421
from apps.alerts.signals import alert_group_action_triggered_signal
25-
from apps.alerts.tasks import (
26-
acknowledge_reminder_task,
27-
call_ack_url,
28-
schedule_cache_for_alert_group,
29-
send_alert_group_signal,
30-
unsilence_task,
31-
)
22+
from apps.alerts.tasks import acknowledge_reminder_task, call_ack_url, send_alert_group_signal, unsilence_task
3223
from apps.slack.slack_formatter import SlackFormatter
3324
from apps.user_management.models import User
34-
from common.mixins.use_random_readonly_db_manager_mixin import UseRandomReadonlyDbManagerMixin
3525
from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length
3626
from common.utils import clean_markup, str_or_backup
3727

@@ -108,10 +98,6 @@ def filter(self, *args, **kwargs):
10898
return super().filter(*args, **kwargs, is_archived=False)
10999

110100

111-
class AlertGroupManager(UseRandomReadonlyDbManagerMixin, models.Manager):
112-
pass
113-
114-
115101
class AlertGroupSlackRenderingMixin:
116102
"""
117103
Ideally this mixin should not exist. Instead of this instance of AlertGroupSlackRenderer should be created and used
@@ -134,8 +120,8 @@ def slack_templated_first_alert(self):
134120

135121

136122
class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.Model):
137-
all_objects = AlertGroupManager.from_queryset(AlertGroupQuerySet)()
138-
unarchived_objects = AlertGroupManager.from_queryset(UnarchivedAlertGroupQuerySet)()
123+
all_objects = AlertGroupQuerySet.as_manager()
124+
unarchived_objects = UnarchivedAlertGroupQuerySet.as_manager()
139125

140126
(
141127
NEW,
@@ -242,8 +228,6 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
242228

243229
active_escalation_id = models.CharField(max_length=100, null=True, default=None) # ID generated by celery
244230
active_resolve_calculation_id = models.CharField(max_length=100, null=True, default=None) # ID generated by celery
245-
# ID generated by celery
246-
active_cache_for_web_calculation_id = models.CharField(max_length=100, null=True, default=None)
247231

248232
SILENCE_DELAY_OPTIONS = (
249233
(1800, "30 minutes"),
@@ -315,8 +299,6 @@ def status(self):
315299
related_name="dependent_alert_groups",
316300
)
317301

318-
cached_render_for_web = JSONField(default=dict)
319-
320302
last_unique_unacknowledge_process_id = models.CharField(max_length=100, null=True, default=None)
321303
is_archived = models.BooleanField(default=False)
322304

@@ -404,18 +386,6 @@ def skip_escalation_in_slack(self):
404386
def is_alert_a_resolve_signal(self, alert):
405387
raise NotImplementedError
406388

407-
def cache_for_web(self, organization):
408-
from apps.api.serializers.alert_group import AlertGroupSerializer
409-
410-
# Re-take object to switch connection from readonly db to master.
411-
_self = AlertGroup.all_objects.get(pk=self.pk)
412-
_self.cached_render_for_web = AlertGroupSerializer(self, context={"organization": organization}).data
413-
self.cached_render_for_web = _self.cached_render_for_web
414-
_self.save(update_fields=["cached_render_for_web"])
415-
416-
def schedule_cache_for_web(self):
417-
schedule_cache_for_alert_group.apply_async((self.pk,))
418-
419389
@property
420390
def permalink(self):
421391
if self.slack_message is not None:
@@ -425,10 +395,6 @@ def permalink(self):
425395
def web_link(self):
426396
return urljoin(self.channel.organization.web_link, f"?page=incident&id={self.public_primary_key}")
427397

428-
@property
429-
def alerts_count(self):
430-
return self.alerts.count()
431-
432398
@property
433399
def happened_while_maintenance(self):
434400
return self.root_alert_group is not None and self.root_alert_group.maintenance_uuid is not None
@@ -449,10 +415,6 @@ def acknowledge_by_user(self, user: User, action_source: Optional[str] = None) -
449415
self.unresolve()
450416
self.log_records.create(type=AlertGroupLogRecord.TYPE_UN_RESOLVED, author=user, reason="Acknowledge button")
451417

452-
# clear resolve report cache
453-
cache_key = "render_after_resolve_report_json_{}".format(self.pk)
454-
cache.delete(cache_key)
455-
456418
self.acknowledge(acknowledged_by_user=user, acknowledged_by=AlertGroup.USER)
457419
self.stop_escalation()
458420
if self.is_root_alert_group:
@@ -673,9 +635,6 @@ def un_resolve_by_user(self, user: User, action_source: Optional[str] = None) ->
673635
self.unresolve()
674636
log_record = self.log_records.create(type=AlertGroupLogRecord.TYPE_UN_RESOLVED, author=user)
675637

676-
# clear resolve report cache
677-
self.drop_cached_after_resolve_report_json()
678-
679638
if self.is_root_alert_group:
680639
self.start_escalation_if_needed()
681640

@@ -848,10 +807,6 @@ def silence_by_user(self, user: User, silence_delay: Optional[int], action_sourc
848807
self.unresolve()
849808
self.log_records.create(type=AlertGroupLogRecord.TYPE_UN_RESOLVED, author=user, reason="Silence button")
850809

851-
# clear resolve report cache
852-
cache_key = "render_after_resolve_report_json_{}".format(self.pk)
853-
cache.delete(cache_key)
854-
855810
if self.acknowledged:
856811
self.unacknowledge()
857812
self.log_records.create(type=AlertGroupLogRecord.TYPE_UN_ACK, author=user, reason="Silence button")
@@ -1060,8 +1015,6 @@ def bulk_acknowledge(user: User, alert_groups: "QuerySet[AlertGroup]") -> None:
10601015
author=user,
10611016
reason="Bulk action acknowledge",
10621017
)
1063-
# clear resolve report cache
1064-
alert_group.drop_cached_after_resolve_report_json()
10651018

10661019
for alert_group in alert_groups_to_unsilence_before_acknowledge_list:
10671020
alert_group.log_records.create(
@@ -1194,8 +1147,6 @@ def bulk_restart(user: User, alert_groups: "QuerySet[AlertGroup]") -> None:
11941147
reason="Bulk action restart",
11951148
)
11961149

1197-
alert_group.drop_cached_after_resolve_report_json()
1198-
11991150
if alert_group.is_root_alert_group:
12001151
alert_group.start_escalation_if_needed()
12011152

@@ -1293,7 +1244,6 @@ def bulk_silence(user: User, alert_groups: "QuerySet[AlertGroup]", silence_delay
12931244
author=user,
12941245
reason="Bulk action silence",
12951246
)
1296-
alert_group.drop_cached_after_resolve_report_json()
12971247

12981248
for alert_group in alert_groups_to_unsilence_before_silence_list:
12991249
alert_group.log_records.create(
@@ -1483,7 +1433,7 @@ def get_acknowledge_text(self, mention_user=False):
14831433
else:
14841434
return "Acknowledged"
14851435

1486-
def non_cached_after_resolve_report_json(self):
1436+
def render_after_resolve_report_json(self):
14871437
AlertGroupLogRecord = apps.get_model("alerts", "AlertGroupLogRecord")
14881438
UserNotificationPolicyLogRecord = apps.get_model("base", "UserNotificationPolicyLogRecord")
14891439
ResolutionNote = apps.get_model("alerts", "ResolutionNote")
@@ -1501,21 +1451,6 @@ def non_cached_after_resolve_report_json(self):
15011451
result_log_report.append(log_record.render_log_line_json())
15021452
return result_log_report
15031453

1504-
def render_after_resolve_report_json(self):
1505-
cache_key = "render_after_resolve_report_json_{}".format(self.pk)
1506-
1507-
# cache.get_or_set in some cases returns None, so use get and set cache methods separately
1508-
log_report = cache.get(cache_key)
1509-
if log_report is None:
1510-
log_report = self.non_cached_after_resolve_report_json()
1511-
cache.set(cache_key, log_report)
1512-
return log_report
1513-
1514-
def drop_cached_after_resolve_report_json(self):
1515-
cache_key = "render_after_resolve_report_json_{}".format(self.pk)
1516-
if cache_key in cache:
1517-
cache.delete(cache_key)
1518-
15191454
@property
15201455
def has_resolution_notes(self):
15211456
return self.resolution_notes.exists()
@@ -1595,14 +1530,3 @@ def last_stop_escalation_log(self):
15951530
)
15961531

15971532
return stop_escalation_log
1598-
1599-
1600-
@receiver(post_save, sender=AlertGroup)
1601-
def listen_for_alert_group_model_save(sender, instance, created, *args, **kwargs):
1602-
if (
1603-
kwargs is not None
1604-
and "update_fields" in kwargs
1605-
and kwargs["update_fields"] is dict
1606-
and "cached_render_for_web" not in kwargs["update_fields"]
1607-
):
1608-
transaction.on_commit(instance.schedule_cache_for_alert_group)

engine/apps/alerts/models/alert_group_log_record.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import humanize
55
from django.apps import apps
6-
from django.db import models, transaction
6+
from django.db import models
77
from django.db.models import JSONField
88
from django.db.models.signals import post_save
99
from django.dispatch import receiver
@@ -546,7 +546,6 @@ def get_step_specific_info(self):
546546

547547
@receiver(post_save, sender=AlertGroupLogRecord)
548548
def listen_for_alertgrouplogrecord(sender, instance, created, *args, **kwargs):
549-
instance.alert_group.drop_cached_after_resolve_report_json()
550549
if instance.type != AlertGroupLogRecord.TYPE_DELETED:
551550
if not instance.alert_group.is_maintenance_incident:
552551
alert_group_pk = instance.alert_group.pk
@@ -555,6 +554,3 @@ def listen_for_alertgrouplogrecord(sender, instance, created, *args, **kwargs):
555554
f"alert group event: {instance.get_type_display()}"
556555
)
557556
send_update_log_report_signal.apply_async(kwargs={"alert_group_pk": alert_group_pk}, countdown=8)
558-
559-
logger.info(f"Recalculate AG cache. Reason: save alert_group_log_record model {instance.pk}")
560-
transaction.on_commit(instance.alert_group.schedule_cache_for_web)

0 commit comments

Comments
 (0)