Skip to content

Commit 423469b

Browse files
committed
feat(panic): add panic button
panic button - block all sample rate to 0 - prevent bump action
1 parent 6b482a3 commit 423469b

File tree

8 files changed

+128
-21
lines changed

8 files changed

+128
-21
lines changed

commitlint.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ const scope = [
55
"route",
66
"task",
77
"validator",
8-
"metrics"
8+
"metrics",
9+
"panic"
910
];
1011

1112
module.exports = {

controller/sentry/admin.py

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
add_form_to_action,
55
confirm_action,
66
)
7+
from django.conf import settings
78
from django.contrib import admin
89
from django.contrib.auth import get_permission_codename
10+
from django.core.cache import cache
911
from django.db import models
1012
from django.utils import timezone
1113
from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin
@@ -16,6 +18,8 @@
1618
from controller.sentry.models import App
1719
from controller.sentry.utils import invalidate_cache
1820

21+
admin.site
22+
1923

2024
@admin.register(App)
2125
class AppAdmin(
@@ -33,6 +37,8 @@ class AppAdmin(
3337
"default_sample_rate",
3438
"active_sample_rate",
3539
"active_window_end",
40+
"wsgi_collect_metrics",
41+
"celery_collect_metrics",
3642
]
3743

3844
search_fields = [
@@ -74,28 +80,76 @@ class AppAdmin(
7480
},
7581
],
7682
]
77-
actions = ["bump"]
78-
changelist_actions = []
79-
change_actions = ["bump"]
83+
actions = ["bump_sample_rate"]
84+
changelist_actions = ["panic", "unpanic"]
85+
change_actions = ["bump_sample_rate"]
86+
87+
def get_changelist_actions(self, request):
88+
allowed_actions = []
89+
for action in self.changelist_actions:
90+
if getattr(self, f"has_{action}_permission")(request):
91+
allowed_actions.append(action)
92+
return allowed_actions
93+
94+
def get_change_actions(self, request, object_id, form_url):
95+
allowed_actions = []
96+
for action in self.change_actions:
97+
if getattr(self, f"has_{action}_permission")(request):
98+
allowed_actions.append(action)
99+
return allowed_actions
80100

81101
@takes_instance_or_queryset
82102
@add_form_to_action(BumpForm)
83103
@confirm_action()
84104
@admin.action(description="Bump Sample Rate")
85-
def bump(self, request, queryset, form: BumpForm = None):
105+
def bump_sample_rate(self, request, queryset, form: BumpForm = None):
86106
new_date = timezone.now() + form.cleaned_data["duration"]
87107
queryset.update(
88108
active_sample_rate=form.cleaned_data["new_sample_rate"],
89109
active_window_end=new_date,
90110
)
91111

92-
bump.allowed_permissions = ("bump_sample_rate",)
112+
bump_sample_rate.allowed_permissions = ("bump_sample_rate",)
93113

94114
def has_bump_sample_rate_permission(self, request):
95115
"""Does the user have the bump permission?"""
96116
opts = self.opts
97117
codename = get_permission_codename("bump_sample_rate", opts)
98-
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
118+
119+
panic = cache.get(settings.PANIC_KEY)
120+
return not panic and request.user.has_perm("%s.%s" % (opts.app_label, codename))
121+
122+
@takes_instance_or_queryset
123+
@confirm_action(display_queryset=False)
124+
@admin.action(description="Panic")
125+
def panic(self, request, queryset):
126+
cache.set(settings.PANIC_KEY, True, timeout=None)
127+
128+
panic.allowed_permissions = ("panic",)
129+
panic.attrs = {"style": "background-color: red;"}
130+
131+
def has_panic_permission(self, request):
132+
"""Does the user have the panic permission?"""
133+
panic = cache.get(settings.PANIC_KEY)
134+
opts = self.opts
135+
codename = get_permission_codename("panic", opts)
136+
return not panic and request.user.has_perm("%s.%s" % (opts.app_label, codename))
137+
138+
@takes_instance_or_queryset
139+
@confirm_action(display_queryset=False)
140+
@admin.action(description="UnPanic")
141+
def unpanic(self, request, queryset):
142+
cache.delete(settings.PANIC_KEY)
143+
144+
unpanic.allowed_permissions = ("unpanic",)
145+
unpanic.attrs = {"style": "background-color: green;"}
146+
147+
def has_unpanic_permission(self, request):
148+
"""Does the user have the panic permission?"""
149+
panic = cache.get(settings.PANIC_KEY)
150+
opts = self.opts
151+
codename = get_permission_codename("panic", opts)
152+
return panic and request.user.has_perm("%s.%s" % (opts.app_label, codename))
99153

100154
def save_model(self, request, obj, form, change) -> None:
101155
invalidate_cache(f"/sentry/apps/{obj.reference}/")
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 4.1.5 on 2023-01-12 14:08
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("sentry", "0006_alter_app_options"),
10+
]
11+
12+
operations = [
13+
migrations.AlterModelOptions(
14+
name="app",
15+
options={
16+
"permissions": [
17+
("bump_sample_rate_app", "Can bump sample rate"),
18+
("panic_app", "Panic! Set all sample rate to 0"),
19+
]
20+
},
21+
),
22+
]

controller/sentry/models.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,7 @@ def merge(self, validated_data):
5555
self.last_seen = timezone.now()
5656

5757
class Meta:
58-
permissions = [("bump_sample_rate_app", "Can bump sample rate")]
58+
permissions = [
59+
("bump_sample_rate_app", "Can bump sample rate"),
60+
("panic_app", "Panic! Set all sample rate to 0"),
61+
]

controller/sentry/utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,7 @@ def invalidate_cache(path=""):
3838
raise ValueError("failed to create cache_key")
3939
except (ValueError, Exception) as e:
4040
return (False, e)
41+
42+
43+
def is_panic_activated(request):
44+
return {"PANIC": cache.get(settings.PANIC_KEY)}

controller/sentry/views.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from django.conf import settings
2+
from django.core.cache import cache
23
from django.utils import timezone
34
from django.utils.decorators import method_decorator
45
from django.views.decorators.cache import cache_page
@@ -19,11 +20,14 @@ class AppViewSet(viewsets.ModelViewSet):
1920
@method_decorator(cache_page(settings.APP_CACHE_TIMEOUT))
2021
def retrieve(self, request, *args, **kwargs):
2122
app, _ = App.objects.get_or_create(**kwargs)
23+
panic = cache.get(settings.PANIC_KEY)
2224
now = timezone.now()
2325
app.last_seen = now
2426
if app.active_window_end and app.active_window_end < now:
2527
app.active_sample_rate = app.default_sample_rate
2628
app.save()
29+
if panic:
30+
app.active_sample_rate = 0.0
2731
serializer = self.get_serializer(app)
2832
return Response(serializer.data)
2933

controller/settings.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,15 @@
8989
TEMPLATES = [
9090
{
9191
"BACKEND": "django.template.backends.django.DjangoTemplates",
92-
"DIRS": [],
92+
"DIRS": [BASE_DIR / "templates"],
9393
"APP_DIRS": True,
9494
"OPTIONS": {
9595
"context_processors": [
9696
"django.template.context_processors.debug",
9797
"django.template.context_processors.request",
9898
"django.contrib.auth.context_processors.auth",
9999
"django.contrib.messages.context_processors.messages",
100+
"controller.sentry.utils.is_panic_activated",
100101
],
101102
},
102103
},
@@ -163,19 +164,19 @@
163164
CONN_MAX_AGE = None
164165
APP_CACHE_TIMEOUT = 0
165166

166-
if not DEBUG:
167-
APP_CACHE_TIMEOUT = int(os.getenv("APP_CACHE_TIMEOUT", "600"))
168-
CACHES = {
169-
"default": {
170-
"BACKEND": "django.core.cache.backends.redis.RedisCache",
171-
"LOCATION": os.getenv("CACHE_REDIS_URL", "redis://127.0.0.1:6379"),
172-
"OPTIONS": {
173-
"parser_class": "redis.connection.PythonParser",
174-
"pool_class": "redis.BlockingConnectionPool",
175-
},
176-
"TIMEOUT": int(os.getenv("CACHE_TIMEOUT", "120")),
177-
}
167+
168+
APP_CACHE_TIMEOUT = int(os.getenv("APP_CACHE_TIMEOUT", "600"))
169+
CACHES = {
170+
"default": {
171+
"BACKEND": "django.core.cache.backends.redis.RedisCache",
172+
"LOCATION": os.getenv("CACHE_REDIS_URL", "redis://127.0.0.1:6379"),
173+
"OPTIONS": {
174+
"parser_class": "redis.connection.PythonParser",
175+
"pool_class": "redis.BlockingConnectionPool",
176+
},
177+
"TIMEOUT": int(os.getenv("CACHE_TIMEOUT", "120")),
178178
}
179+
}
179180

180181

181182
DEFAULT_SAMPLE_RATE = float(os.getenv("DEFAULT_SAMPLE_RATE", "0.1"))
@@ -197,3 +198,5 @@
197198
MAX_BUMP_TIME_SEC = int(os.getenv("MAX_BUMP_TIME_SEC", "0"))
198199
if MAX_BUMP_TIME_SEC == 0:
199200
MAX_BUMP_TIME_SEC = 30 * 60 # 30 minutes
201+
202+
PANIC_KEY = "PANIC"

templates/admin/base_site.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{% extends "admin/base_site.html" %}
2+
3+
4+
{% block branding %}
5+
{{ block.super }}
6+
7+
{% if PANIC %}
8+
9+
<style>
10+
:root {
11+
--header-bg: orangered;
12+
}
13+
</style>
14+
<b>Panic Mode Activated</b>
15+
{% endif %}
16+
{% endblock %}

0 commit comments

Comments
 (0)