Skip to content

Commit

Permalink
Merge pull request #266 from grafana/dev
Browse files Browse the repository at this point in the history
Merge dev to main
  • Loading branch information
vstpme authored Jul 21, 2022
2 parents b94e099 + 11a3a79 commit 684c774
Show file tree
Hide file tree
Showing 22 changed files with 186 additions and 29 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## v1.0.8 (2022-07-21)
- Frontend bug fixes & improvements
- Support regex_replace() in templates

## v1.0.7 (2022-07-18)
- Backend & frontend bug fixes
- Deployment improvements
Expand Down
11 changes: 8 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ services:
oncall_db_migration:
condition: service_completed_successfully
rabbitmq:
condition: service_started
condition: service_healthy
redis:
condition: service_started

Expand Down Expand Up @@ -64,7 +64,7 @@ services:
oncall_db_migration:
condition: service_completed_successfully
rabbitmq:
condition: service_started
condition: service_healthy
redis:
condition: service_started

Expand Down Expand Up @@ -92,7 +92,7 @@ services:
mysql:
condition: service_healthy
rabbitmq:
condition: service_started
condition: service_healthy

mysql:
image: mysql:5.7
Expand Down Expand Up @@ -133,6 +133,11 @@ services:
RABBITMQ_DEFAULT_USER: "rabbitmq"
RABBITMQ_DEFAULT_PASS: $RABBITMQ_PASSWORD
RABBITMQ_DEFAULT_VHOST: "/"
healthcheck:
test: rabbitmq-diagnostics -q ping
interval: 30s
timeout: 30s
retries: 3

mysql_to_create_grafana_db:
image: mysql:5.7
Expand Down
1 change: 1 addition & 0 deletions docs/sources/integrations/create-custom-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,4 @@ Built-in functions:
- `tojson_pretty` - JSON prettified
- `iso8601_to_time` - converts time from iso8601 (`2015-02-17T18:30:20.000Z`) to datetime
- `datetimeformat` - converts time from datetime to the given format (`%H:%M / %d-%m-%Y` by default)
- `regex_replace` - performs a regex find and replace
2 changes: 1 addition & 1 deletion docs/sources/open-source.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ We'll always be happy to provide assistance with production deployment in [our c

## Update Grafana OnCall OSS
To update an OSS installation of Grafana OnCall, please see the update docs:
- **Hobby** playground environment: [README.md](https://github.com/grafana/oncall#update)
- **Hobby** playground environment: [README.md](https://github.com/grafana/oncall#update-version)
- **Production** Helm environment: [Helm update](https://github.com/grafana/oncall/tree/dev/helm/oncall#update)

## Slack Setup
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from apps.alerts.incident_appearance.renderers.base_renderer import AlertBaseRenderer, AlertGroupBaseRenderer
from apps.alerts.incident_appearance.templaters import AlertClassicMarkdownTemplater
from common.utils import str_or_backup


class AlertClassicMarkdownRenderer(AlertBaseRenderer):
@property
def templater_class(self):
return AlertClassicMarkdownTemplater

def render(self):
templated_alert = self.templated_alert
rendered_alert = {
"title": str_or_backup(templated_alert.title, "Alert"),
"message": str_or_backup(templated_alert.message, ""),
"image_url": str_or_backup(templated_alert.image_url, None),
"source_link": str_or_backup(templated_alert.source_link, None),
}
return rendered_alert


class AlertGroupClassicMarkdownRenderer(AlertGroupBaseRenderer):
def __init__(self, alert_group, alert=None):
if alert is None:
alert = alert_group.alerts.last()

super().__init__(alert_group, alert)

@property
def alert_renderer_class(self):
return AlertClassicMarkdownRenderer

def render(self):
return self.alert_renderer.render()
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .alert_templater import TemplateLoader # noqa: F401
from .classic_markdown_templater import AlertClassicMarkdownTemplater # noqa: F401
from .email_templater import AlertEmailTemplater # noqa: F401
from .phone_call_templater import AlertPhoneCallTemplater # noqa: F401
from .slack_templater import AlertSlackTemplater # noqa: F401
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from apps.alerts.incident_appearance.templaters.alert_templater import AlertTemplater


class AlertClassicMarkdownTemplater(AlertTemplater):
RENDER_FOR = "web"

def _render_for(self):
return self.RENDER_FOR

def _postformat(self, templated_alert):
if templated_alert.title:
templated_alert.title = self._slack_format(templated_alert.title)
if templated_alert.message:
templated_alert.message = self._slack_format(templated_alert.message)
return templated_alert

def _slack_format(self, data):
sf = self.slack_formatter
sf.hyperlink_mention_format = "[{title}]({url})"
return sf.format(data)
23 changes: 15 additions & 8 deletions engine/apps/alerts/migrations/0004_auto_20220711_1106.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,25 @@


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

dependencies = [
('alerts', '0003_grafanaalertingcontactpoint_datasource_uid'),
]

operations = [
migrations.RemoveField(
model_name='alertgroup',
name='active_cache_for_web_calculation_id',
),
migrations.RemoveField(
model_name='alertgroup',
name='cached_render_for_web',
),
# migrations.RemoveField(
# model_name='alertgroup',
# name='active_cache_for_web_calculation_id',
# ),
# migrations.RemoveField(
# model_name='alertgroup',
# name='cached_render_for_web',
# ),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 3.2.13 on 2022-07-20 09:04

from django.db import migrations, models, OperationalError


class AddFieldIfNotExists(migrations.AddField):
"""
Adds a field and ignores "duplicate column" error in case the field already exists.
When migrating back it will not delete the field.
"""

def database_forwards(self, app_label, schema_editor, from_state, to_state):
try:
super().database_forwards(app_label, schema_editor, from_state, to_state)
except OperationalError:
pass

def database_backwards(self, app_label, schema_editor, from_state, to_state):
pass


class Migration(migrations.Migration):
"""
This migration tries to create two fields cached_render_for_web and active_cache_for_web_calculation_id.
In case these fields already exist, this migration will do nothing.
In case the database was already affected by the previous version of the 0004 migration,
it will recreate these fields.
"""

dependencies = [
('alerts', '0004_auto_20220711_1106'),
]

operations = [
AddFieldIfNotExists(
model_name='alertgroup',
name='cached_render_for_web',
field=models.JSONField(default=dict),
),
AddFieldIfNotExists(
model_name='alertgroup',
name='active_cache_for_web_calculation_id',
field=models.CharField(default=None, max_length=100, null=True),
),
]
4 changes: 4 additions & 0 deletions engine/apps/alerts/models/alert_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ def status(self):
related_name="dependent_alert_groups",
)

# cached_render_for_web and active_cache_for_web_calculation_id are deprecated
cached_render_for_web = models.JSONField(default=dict)
active_cache_for_web_calculation_id = models.CharField(max_length=100, null=True, default=None)

last_unique_unacknowledge_process_id = models.CharField(max_length=100, null=True, default=None)
is_archived = models.BooleanField(default=False)

Expand Down
20 changes: 12 additions & 8 deletions engine/apps/alerts/tasks/notify_user.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import random
import time

from django.apps import apps
Expand Down Expand Up @@ -356,37 +355,42 @@ def perform_notification(log_record_pk):
message = f"{AlertGroupWebRenderer(alert_group).render().get('title', 'Incident')}"
thread_id = f"{alert_group.channel.organization.public_primary_key}:{alert_group.public_primary_key}"
devices_to_notify = APNSDevice.objects.filter(user_id=user.pk)
sounds = ["alarm.aiff", "operation.aiff"]
devices_to_notify.send_message(
message,
thread_id=thread_id,
category="USER_NEW_INCIDENT",
sound={"critical": 1, "name": f"{random.choice(sounds)}"},
extra={
"orgId": f"{alert_group.channel.organization.public_primary_key}",
"orgName": f"{alert_group.channel.organization.stack_slug}",
"incidentId": f"{alert_group.public_primary_key}",
"status": f"{alert_group.status}",
"interruption-level": "critical",
"aps": {
"alert": f"{message}",
"sound": "bingbong.aiff",
},
},
)

elif notification_channel == UserNotificationPolicy.NotificationChannel.MOBILE_PUSH_CRITICAL:
message = f"!!! {AlertGroupWebRenderer(alert_group).render().get('title', 'Incident')}"
message = f"{AlertGroupWebRenderer(alert_group).render().get('title', 'Incident')}"
thread_id = f"{alert_group.channel.organization.public_primary_key}:{alert_group.public_primary_key}"
devices_to_notify = APNSDevice.objects.filter(user_id=user.pk)
sounds = ["ambulance.aiff"]
devices_to_notify.send_message(
message,
thread_id=thread_id,
category="USER_NEW_INCIDENT",
sound={"critical": 1, "name": f"{random.choice(sounds)}"},
extra={
"orgId": f"{alert_group.channel.organization.public_primary_key}",
"orgName": f"{alert_group.channel.organization.stack_slug}",
"incidentId": f"{alert_group.public_primary_key}",
"status": f"{alert_group.status}",
"interruption-level": "critical",
"aps": {
"alert": f"Critical page: {message}",
# This is disabled until we gain the Critical Alerts Api permission from apple
# "interruption-level": "critical",
"interruption-level": "time-sensitive",
"sound": "ambulance.aiff",
},
},
)
else:
Expand Down
6 changes: 6 additions & 0 deletions engine/apps/api/serializers/alert_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from rest_framework import serializers

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

alerts_count = serializers.IntegerField(read_only=True)
render_for_web = serializers.SerializerMethodField()
render_for_classic_markdown = serializers.SerializerMethodField()

PREFETCH_RELATED = [
"dependent_alert_groups",
Expand Down Expand Up @@ -78,6 +80,7 @@ class Meta:
"silenced_until",
"related_users",
"render_for_web",
"render_for_classic_markdown",
"dependent_alert_groups",
"root_alert_group",
"status",
Expand All @@ -86,6 +89,9 @@ class Meta:
def get_render_for_web(self, obj):
return AlertGroupWebRenderer(obj, obj.last_alert).render()

def get_render_for_classic_markdown(self, obj):
return AlertGroupClassicMarkdownRenderer(obj).render()

def get_related_users(self, obj):
users_ids = set()
users = []
Expand Down
6 changes: 5 additions & 1 deletion engine/apps/api/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,11 @@ class UserView(
mixins.ListModelMixin,
viewsets.GenericViewSet,
):
authentication_classes = (PluginAuthentication,)
authentication_classes = (
MobileAppAuthTokenAuthentication,
PluginAuthentication,
)

permission_classes = (IsAuthenticated, ActionPermission)

# Non-admin users are allowed to list and retrieve users
Expand Down
8 changes: 8 additions & 0 deletions engine/common/jinja_templater/filters.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import re

from django.utils.dateparse import parse_datetime

Expand All @@ -22,3 +23,10 @@ def to_pretty_json(value):
return json.dumps(value, sort_keys=True, indent=4, separators=(",", ": "), ensure_ascii=False)
except (ValueError, AttributeError, TypeError):
return None


def regex_replace(value, find, replace):
try:
return re.sub(find, replace, value)
except (ValueError, AttributeError, TypeError):
return None
3 changes: 2 additions & 1 deletion engine/common/jinja_templater/jinja_template_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
from jinja2 import BaseLoader
from jinja2.sandbox import SandboxedEnvironment

from .filters import datetimeformat, iso8601_to_time, to_pretty_json
from .filters import datetimeformat, iso8601_to_time, regex_replace, to_pretty_json

jinja_template_env = SandboxedEnvironment(loader=BaseLoader())

jinja_template_env.filters["datetimeformat"] = datetimeformat
jinja_template_env.filters["iso8601_to_time"] = iso8601_to_time
jinja_template_env.filters["tojson_pretty"] = to_pretty_json
jinja_template_env.globals["time"] = timezone.now
jinja_template_env.filters["regex_replace"] = regex_replace
7 changes: 7 additions & 0 deletions engine/common/tests/test_regex_replace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from common.jinja_templater.filters import regex_replace


def test_regex_replace_drop_field():
original = "[ var='D0' metric='my_metric' labels={} value=140 ]"
expected = "[ metric='my_metric' labels={} value=140 ]"
assert regex_replace(original, "var='[a-zA-Z0-9]+' ", "") == expected
4 changes: 3 additions & 1 deletion engine/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ django-log-request-id==1.6.0
django-polymorphic==3.0.0
django-rest-polymorphic==0.1.9
pre-commit==2.15.0
https://github.com/iskhakov/django-push-notifications/archive/refs/tags/2.0.0-hotfix-4.tar.gz
django-push-notifications==3.0.0
django-mirage-field==1.3.0
django-mysql==4.6.0
PyMySQL==1.0.2
emoji==1.7.0
apns2==0.7.2

3 changes: 2 additions & 1 deletion engine/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,9 @@
"APNS_TOPIC": os.environ.get("APNS_TOPIC", None),
"APNS_AUTH_KEY_ID": os.environ.get("APNS_AUTH_KEY_ID", None),
"APNS_TEAM_ID": os.environ.get("APNS_TEAM_ID", None),
"APNS_USE_SANDBOX": True,
"APNS_USE_SANDBOX": getenv_boolean("APNS_USE_SANDBOX", True),
"USER_MODEL": "user_management.User",
"UPDATE_ON_DUPLICATE_REG_ID": True,
}

SELF_HOSTED_SETTINGS = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ export const PluginConfigPage = (props: Props) => {
get_sync_response.version && get_sync_response.license
? ` (${get_sync_response.license}, ${get_sync_response.version})`
: '';
setPluginStatusMessage(`Connected to OnCall${versionInfo}: ${plugin.meta.jsonData.onCallApiUrl}`);
setPluginStatusMessage(
`Connected to OnCall${versionInfo}\n - OnCall URL: ${plugin.meta.jsonData.onCallApiUrl}\n - Grafana URL: ${plugin.meta.jsonData.grafanaUrl}`
);
setIsSelfHostedInstall(plugin.meta.jsonData?.license === 'OpenSource');
setPluginStatusOk(true);
} else {
Expand Down
Loading

0 comments on commit 684c774

Please sign in to comment.