Skip to content

Commit 684c774

Browse files
authored
Merge pull request #266 from grafana/dev
Merge dev to main
2 parents b94e099 + 11a3a79 commit 684c774

File tree

22 files changed

+186
-29
lines changed

22 files changed

+186
-29
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change Log
22

3+
## v1.0.8 (2022-07-21)
4+
- Frontend bug fixes & improvements
5+
- Support regex_replace() in templates
6+
37
## v1.0.7 (2022-07-18)
48
- Backend & frontend bug fixes
59
- Deployment improvements

docker-compose.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ services:
2929
oncall_db_migration:
3030
condition: service_completed_successfully
3131
rabbitmq:
32-
condition: service_started
32+
condition: service_healthy
3333
redis:
3434
condition: service_started
3535

@@ -64,7 +64,7 @@ services:
6464
oncall_db_migration:
6565
condition: service_completed_successfully
6666
rabbitmq:
67-
condition: service_started
67+
condition: service_healthy
6868
redis:
6969
condition: service_started
7070

@@ -92,7 +92,7 @@ services:
9292
mysql:
9393
condition: service_healthy
9494
rabbitmq:
95-
condition: service_started
95+
condition: service_healthy
9696

9797
mysql:
9898
image: mysql:5.7
@@ -133,6 +133,11 @@ services:
133133
RABBITMQ_DEFAULT_USER: "rabbitmq"
134134
RABBITMQ_DEFAULT_PASS: $RABBITMQ_PASSWORD
135135
RABBITMQ_DEFAULT_VHOST: "/"
136+
healthcheck:
137+
test: rabbitmq-diagnostics -q ping
138+
interval: 30s
139+
timeout: 30s
140+
retries: 3
136141

137142
mysql_to_create_grafana_db:
138143
image: mysql:5.7

docs/sources/integrations/create-custom-templates.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,4 @@ Built-in functions:
159159
- `tojson_pretty` - JSON prettified
160160
- `iso8601_to_time` - converts time from iso8601 (`2015-02-17T18:30:20.000Z`) to datetime
161161
- `datetimeformat` - converts time from datetime to the given format (`%H:%M / %d-%m-%Y` by default)
162+
- `regex_replace` - performs a regex find and replace

docs/sources/open-source.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ We'll always be happy to provide assistance with production deployment in [our c
3131

3232
## Update Grafana OnCall OSS
3333
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)
34+
- **Hobby** playground environment: [README.md](https://github.com/grafana/oncall#update-version)
3535
- **Production** Helm environment: [Helm update](https://github.com/grafana/oncall/tree/dev/helm/oncall#update)
3636

3737
## Slack Setup
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from apps.alerts.incident_appearance.renderers.base_renderer import AlertBaseRenderer, AlertGroupBaseRenderer
2+
from apps.alerts.incident_appearance.templaters import AlertClassicMarkdownTemplater
3+
from common.utils import str_or_backup
4+
5+
6+
class AlertClassicMarkdownRenderer(AlertBaseRenderer):
7+
@property
8+
def templater_class(self):
9+
return AlertClassicMarkdownTemplater
10+
11+
def render(self):
12+
templated_alert = self.templated_alert
13+
rendered_alert = {
14+
"title": str_or_backup(templated_alert.title, "Alert"),
15+
"message": str_or_backup(templated_alert.message, ""),
16+
"image_url": str_or_backup(templated_alert.image_url, None),
17+
"source_link": str_or_backup(templated_alert.source_link, None),
18+
}
19+
return rendered_alert
20+
21+
22+
class AlertGroupClassicMarkdownRenderer(AlertGroupBaseRenderer):
23+
def __init__(self, alert_group, alert=None):
24+
if alert is None:
25+
alert = alert_group.alerts.last()
26+
27+
super().__init__(alert_group, alert)
28+
29+
@property
30+
def alert_renderer_class(self):
31+
return AlertClassicMarkdownRenderer
32+
33+
def render(self):
34+
return self.alert_renderer.render()

engine/apps/alerts/incident_appearance/templaters/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .alert_templater import TemplateLoader # noqa: F401
2+
from .classic_markdown_templater import AlertClassicMarkdownTemplater # noqa: F401
23
from .email_templater import AlertEmailTemplater # noqa: F401
34
from .phone_call_templater import AlertPhoneCallTemplater # noqa: F401
45
from .slack_templater import AlertSlackTemplater # noqa: F401
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from apps.alerts.incident_appearance.templaters.alert_templater import AlertTemplater
2+
3+
4+
class AlertClassicMarkdownTemplater(AlertTemplater):
5+
RENDER_FOR = "web"
6+
7+
def _render_for(self):
8+
return self.RENDER_FOR
9+
10+
def _postformat(self, templated_alert):
11+
if templated_alert.title:
12+
templated_alert.title = self._slack_format(templated_alert.title)
13+
if templated_alert.message:
14+
templated_alert.message = self._slack_format(templated_alert.message)
15+
return templated_alert
16+
17+
def _slack_format(self, data):
18+
sf = self.slack_formatter
19+
sf.hyperlink_mention_format = "[{title}]({url})"
20+
return sf.format(data)

engine/apps/alerts/migrations/0004_auto_20220711_1106.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,25 @@
44

55

66
class Migration(migrations.Migration):
7+
"""
8+
The previous version of this migration removes two fields:
9+
cached_render_for_web and active_cache_for_web_calculation_id.
10+
Now it doesn't do anything because it can be very slow and even fail on write heavy alertgroup table.
11+
This migration was released in version 1.0.7, so in order to bring back these fields in the later version
12+
there's a 0005 migration. Please see the next migration in alerts: 0005_alertgroup_cached_render_for_web.py
13+
"""
714

815
dependencies = [
916
('alerts', '0003_grafanaalertingcontactpoint_datasource_uid'),
1017
]
1118

1219
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-
),
20+
# migrations.RemoveField(
21+
# model_name='alertgroup',
22+
# name='active_cache_for_web_calculation_id',
23+
# ),
24+
# migrations.RemoveField(
25+
# model_name='alertgroup',
26+
# name='cached_render_for_web',
27+
# ),
2128
]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Generated by Django 3.2.13 on 2022-07-20 09:04
2+
3+
from django.db import migrations, models, OperationalError
4+
5+
6+
class AddFieldIfNotExists(migrations.AddField):
7+
"""
8+
Adds a field and ignores "duplicate column" error in case the field already exists.
9+
When migrating back it will not delete the field.
10+
"""
11+
12+
def database_forwards(self, app_label, schema_editor, from_state, to_state):
13+
try:
14+
super().database_forwards(app_label, schema_editor, from_state, to_state)
15+
except OperationalError:
16+
pass
17+
18+
def database_backwards(self, app_label, schema_editor, from_state, to_state):
19+
pass
20+
21+
22+
class Migration(migrations.Migration):
23+
"""
24+
This migration tries to create two fields cached_render_for_web and active_cache_for_web_calculation_id.
25+
In case these fields already exist, this migration will do nothing.
26+
In case the database was already affected by the previous version of the 0004 migration,
27+
it will recreate these fields.
28+
"""
29+
30+
dependencies = [
31+
('alerts', '0004_auto_20220711_1106'),
32+
]
33+
34+
operations = [
35+
AddFieldIfNotExists(
36+
model_name='alertgroup',
37+
name='cached_render_for_web',
38+
field=models.JSONField(default=dict),
39+
),
40+
AddFieldIfNotExists(
41+
model_name='alertgroup',
42+
name='active_cache_for_web_calculation_id',
43+
field=models.CharField(default=None, max_length=100, null=True),
44+
),
45+
]

engine/apps/alerts/models/alert_group.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,10 @@ def status(self):
299299
related_name="dependent_alert_groups",
300300
)
301301

302+
# cached_render_for_web and active_cache_for_web_calculation_id are deprecated
303+
cached_render_for_web = models.JSONField(default=dict)
304+
active_cache_for_web_calculation_id = models.CharField(max_length=100, null=True, default=None)
305+
302306
last_unique_unacknowledge_process_id = models.CharField(max_length=100, null=True, default=None)
303307
is_archived = models.BooleanField(default=False)
304308

engine/apps/alerts/tasks/notify_user.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import random
21
import time
32

43
from django.apps import apps
@@ -356,37 +355,42 @@ def perform_notification(log_record_pk):
356355
message = f"{AlertGroupWebRenderer(alert_group).render().get('title', 'Incident')}"
357356
thread_id = f"{alert_group.channel.organization.public_primary_key}:{alert_group.public_primary_key}"
358357
devices_to_notify = APNSDevice.objects.filter(user_id=user.pk)
359-
sounds = ["alarm.aiff", "operation.aiff"]
360358
devices_to_notify.send_message(
361359
message,
362360
thread_id=thread_id,
363361
category="USER_NEW_INCIDENT",
364-
sound={"critical": 1, "name": f"{random.choice(sounds)}"},
365362
extra={
366363
"orgId": f"{alert_group.channel.organization.public_primary_key}",
367364
"orgName": f"{alert_group.channel.organization.stack_slug}",
368365
"incidentId": f"{alert_group.public_primary_key}",
369366
"status": f"{alert_group.status}",
370-
"interruption-level": "critical",
367+
"aps": {
368+
"alert": f"{message}",
369+
"sound": "bingbong.aiff",
370+
},
371371
},
372372
)
373373

374374
elif notification_channel == UserNotificationPolicy.NotificationChannel.MOBILE_PUSH_CRITICAL:
375-
message = f"!!! {AlertGroupWebRenderer(alert_group).render().get('title', 'Incident')}"
375+
message = f"{AlertGroupWebRenderer(alert_group).render().get('title', 'Incident')}"
376376
thread_id = f"{alert_group.channel.organization.public_primary_key}:{alert_group.public_primary_key}"
377377
devices_to_notify = APNSDevice.objects.filter(user_id=user.pk)
378-
sounds = ["ambulance.aiff"]
379378
devices_to_notify.send_message(
380379
message,
381380
thread_id=thread_id,
382381
category="USER_NEW_INCIDENT",
383-
sound={"critical": 1, "name": f"{random.choice(sounds)}"},
384382
extra={
385383
"orgId": f"{alert_group.channel.organization.public_primary_key}",
386384
"orgName": f"{alert_group.channel.organization.stack_slug}",
387385
"incidentId": f"{alert_group.public_primary_key}",
388386
"status": f"{alert_group.status}",
389-
"interruption-level": "critical",
387+
"aps": {
388+
"alert": f"Critical page: {message}",
389+
# This is disabled until we gain the Critical Alerts Api permission from apple
390+
# "interruption-level": "critical",
391+
"interruption-level": "time-sensitive",
392+
"sound": "ambulance.aiff",
393+
},
390394
},
391395
)
392396
else:

engine/apps/api/serializers/alert_group.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from rest_framework import serializers
44

5+
from apps.alerts.incident_appearance.renderers.classic_markdown_renderer import AlertGroupClassicMarkdownRenderer
56
from apps.alerts.incident_appearance.renderers.web_renderer import AlertGroupWebRenderer
67
from apps.alerts.models import AlertGroup
78
from common.api_helpers.mixins import EagerLoadingMixin
@@ -39,6 +40,7 @@ class AlertGroupListSerializer(EagerLoadingMixin, serializers.ModelSerializer):
3940

4041
alerts_count = serializers.IntegerField(read_only=True)
4142
render_for_web = serializers.SerializerMethodField()
43+
render_for_classic_markdown = serializers.SerializerMethodField()
4244

4345
PREFETCH_RELATED = [
4446
"dependent_alert_groups",
@@ -78,6 +80,7 @@ class Meta:
7880
"silenced_until",
7981
"related_users",
8082
"render_for_web",
83+
"render_for_classic_markdown",
8184
"dependent_alert_groups",
8285
"root_alert_group",
8386
"status",
@@ -86,6 +89,9 @@ class Meta:
8689
def get_render_for_web(self, obj):
8790
return AlertGroupWebRenderer(obj, obj.last_alert).render()
8891

92+
def get_render_for_classic_markdown(self, obj):
93+
return AlertGroupClassicMarkdownRenderer(obj).render()
94+
8995
def get_related_users(self, obj):
9096
users_ids = set()
9197
users = []

engine/apps/api/views/user.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,11 @@ class UserView(
101101
mixins.ListModelMixin,
102102
viewsets.GenericViewSet,
103103
):
104-
authentication_classes = (PluginAuthentication,)
104+
authentication_classes = (
105+
MobileAppAuthTokenAuthentication,
106+
PluginAuthentication,
107+
)
108+
105109
permission_classes = (IsAuthenticated, ActionPermission)
106110

107111
# Non-admin users are allowed to list and retrieve users

engine/common/jinja_templater/filters.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import re
23

34
from django.utils.dateparse import parse_datetime
45

@@ -22,3 +23,10 @@ def to_pretty_json(value):
2223
return json.dumps(value, sort_keys=True, indent=4, separators=(",", ": "), ensure_ascii=False)
2324
except (ValueError, AttributeError, TypeError):
2425
return None
26+
27+
28+
def regex_replace(value, find, replace):
29+
try:
30+
return re.sub(find, replace, value)
31+
except (ValueError, AttributeError, TypeError):
32+
return None

engine/common/jinja_templater/jinja_template_env.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
from jinja2 import BaseLoader
33
from jinja2.sandbox import SandboxedEnvironment
44

5-
from .filters import datetimeformat, iso8601_to_time, to_pretty_json
5+
from .filters import datetimeformat, iso8601_to_time, regex_replace, to_pretty_json
66

77
jinja_template_env = SandboxedEnvironment(loader=BaseLoader())
88

99
jinja_template_env.filters["datetimeformat"] = datetimeformat
1010
jinja_template_env.filters["iso8601_to_time"] = iso8601_to_time
1111
jinja_template_env.filters["tojson_pretty"] = to_pretty_json
1212
jinja_template_env.globals["time"] = timezone.now
13+
jinja_template_env.filters["regex_replace"] = regex_replace
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from common.jinja_templater.filters import regex_replace
2+
3+
4+
def test_regex_replace_drop_field():
5+
original = "[ var='D0' metric='my_metric' labels={} value=140 ]"
6+
expected = "[ metric='my_metric' labels={} value=140 ]"
7+
assert regex_replace(original, "var='[a-zA-Z0-9]+' ", "") == expected

engine/requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ django-log-request-id==1.6.0
3636
django-polymorphic==3.0.0
3737
django-rest-polymorphic==0.1.9
3838
pre-commit==2.15.0
39-
https://github.com/iskhakov/django-push-notifications/archive/refs/tags/2.0.0-hotfix-4.tar.gz
39+
django-push-notifications==3.0.0
4040
django-mirage-field==1.3.0
4141
django-mysql==4.6.0
4242
PyMySQL==1.0.2
4343
emoji==1.7.0
44+
apns2==0.7.2
45+

engine/settings/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,8 +405,9 @@
405405
"APNS_TOPIC": os.environ.get("APNS_TOPIC", None),
406406
"APNS_AUTH_KEY_ID": os.environ.get("APNS_AUTH_KEY_ID", None),
407407
"APNS_TEAM_ID": os.environ.get("APNS_TEAM_ID", None),
408-
"APNS_USE_SANDBOX": True,
408+
"APNS_USE_SANDBOX": getenv_boolean("APNS_USE_SANDBOX", True),
409409
"USER_MODEL": "user_management.User",
410+
"UPDATE_ON_DUPLICATE_REG_ID": True,
410411
}
411412

412413
SELF_HOSTED_SETTINGS = {

grafana-plugin/src/containers/PluginConfigPage/PluginConfigPage.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,9 @@ export const PluginConfigPage = (props: Props) => {
149149
get_sync_response.version && get_sync_response.license
150150
? ` (${get_sync_response.license}, ${get_sync_response.version})`
151151
: '';
152-
setPluginStatusMessage(`Connected to OnCall${versionInfo}: ${plugin.meta.jsonData.onCallApiUrl}`);
152+
setPluginStatusMessage(
153+
`Connected to OnCall${versionInfo}\n - OnCall URL: ${plugin.meta.jsonData.onCallApiUrl}\n - Grafana URL: ${plugin.meta.jsonData.grafanaUrl}`
154+
);
153155
setIsSelfHostedInstall(plugin.meta.jsonData?.license === 'OpenSource');
154156
setPluginStatusOk(true);
155157
} else {

0 commit comments

Comments
 (0)