Skip to content

Commit 315a7a7

Browse files
authored
Merge pull request #399 from grafana/dev
Merge dev to main
2 parents 2ee3a27 + 5df716a commit 315a7a7

Some content is hidden

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

50 files changed

+707
-252
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Change Log
22

3+
## v1.0.23 (2022-08-23)
4+
- Bug fixes
5+
36
## v1.0.22 (2022-08-16)
47
- Make STATIC_URL configurable from environment variable
58

docker-compose-developer-pg.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ services:
5353
retries: 10
5454

5555
grafana:
56-
image: "grafana/grafana:9.0.0-beta3"
56+
image: "grafana/grafana:main"
5757
restart: always
5858
mem_limit: 500m
5959
cpus: 0.5

docker-compose-developer.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ services:
4848
condition: service_healthy
4949

5050
grafana:
51-
image: "grafana/grafana:9.0.0-beta3"
51+
image: "grafana/grafana:main"
5252
restart: always
5353
mem_limit: 500m
5454
cpus: 0.5
@@ -65,5 +65,5 @@ services:
6565
ports:
6666
- 3000:3000
6767
depends_on:
68-
mysql-to-create-grafana-db:
68+
mysql:
6969
condition: service_healthy

docs/sources/open-source.md

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ There are three Grafana OnCall OSS environments available:
2525
## Production Environment
2626
We suggest using our official helm chart for the reliable production deployment of Grafana OnCall. It will deploy Grafana OnCall engine and celery workers, along with RabbitMQ cluster, Redis Cluster, and the database.
2727

28+
>**Note:** The Grafana OnCall engine currently supports one instance of the Grafana OnCall plugin at a time.
29+
2830
Check the [helm chart](https://github.com/grafana/oncall/tree/dev/helm/oncall) for more details.
2931

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

engine/apps/alerts/tasks/call_ack_url.py

+24-3
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,34 @@ def call_ack_url(ack_url, alert_group_pk, channel, http_method="GET"):
3030
else None
3131
)
3232

33+
text = "{}".format(debug_message)
34+
footer = "{}".format(info_message)
35+
blocks = [
36+
{
37+
"type": "section",
38+
"block_id": "alert",
39+
"text": {
40+
"type": "mrkdwn",
41+
"text": text,
42+
},
43+
},
44+
{"type": "divider"},
45+
{
46+
"type": "section",
47+
"block_id": "alert",
48+
"text": {
49+
"type": "mrkdwn",
50+
"text": footer,
51+
},
52+
},
53+
]
54+
3355
if channel is not None:
3456
result = sc.api_call(
3557
"chat.postMessage",
3658
channel=channel,
37-
attachments=[
38-
{"callback_id": "alert", "text": "{}".format(debug_message), "footer": "{}".format(info_message)},
39-
],
59+
text=text,
60+
blocks=blocks,
4061
thread_ts=alert_group.slack_message.slack_id,
4162
mrkdwn=True,
4263
)

engine/apps/api/serializers/alert.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,28 @@
55

66

77
class AlertSerializer(serializers.ModelSerializer):
8+
id = serializers.CharField(read_only=True, source="public_primary_key")
89
render_for_web = serializers.SerializerMethodField()
910

1011
class Meta:
1112
model = Alert
1213
fields = [
13-
"title",
14-
"message",
15-
"image_url",
14+
"id",
1615
"link_to_upstream_details",
1716
"render_for_web",
1817
"created_at",
1918
]
2019

2120
def get_render_for_web(self, obj):
2221
return AlertWebRenderer(obj).render()
22+
23+
24+
class AlertRawSerializer(serializers.ModelSerializer):
25+
id = serializers.CharField(read_only=True, source="public_primary_key")
26+
27+
class Meta:
28+
model = Alert
29+
fields = [
30+
"id",
31+
"raw_request_data",
32+
]

engine/apps/api/tests/test_schedules.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,7 @@ def test_merging_same_shift_events(
912912
"is_gap": False,
913913
"priority_level": 1,
914914
"start": start_date + timezone.timedelta(hours=10),
915-
"users": [user_a.username, user_b.username],
915+
"users": sorted([user_a.username, user_b.username]),
916916
"missing_users": [user_c.username],
917917
}
918918
]
@@ -929,7 +929,7 @@ def test_merging_same_shift_events(
929929
"is_gap": e["is_gap"],
930930
"priority_level": e["priority_level"],
931931
"start": e["start"],
932-
"users": [u["display_name"] for u in e["users"]] if e["users"] else None,
932+
"users": sorted([u["display_name"] for u in e["users"]]) if e["users"] else None,
933933
"missing_users": e["missing_users"],
934934
}
935935
for e in response.data["events"]
@@ -950,7 +950,7 @@ def test_merging_same_shift_events(
950950
"is_gap": e["is_gap"],
951951
"priority_level": e["priority_level"],
952952
"start": e["start"],
953-
"users": [u["display_name"] for u in e["users"]] if e["users"] else None,
953+
"users": sorted([u["display_name"] for u in e["users"]]) if e["users"] else None,
954954
"missing_users": e["missing_users"],
955955
}
956956
for e in response.data["events"]

engine/apps/api/tests/test_team.py

+195
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,198 @@ def test_list_teams_permissions(
8585
response = client.get(url, format="json", **make_user_auth_headers(user, token))
8686

8787
assert response.status_code == status.HTTP_200_OK
88+
89+
90+
@pytest.mark.django_db
91+
def test_team_permissions_wrong_team_general(
92+
make_organization,
93+
make_team,
94+
make_alert_group,
95+
make_alert_receive_channel,
96+
make_user,
97+
make_escalation_chain,
98+
make_schedule,
99+
make_custom_action,
100+
make_token_for_organization,
101+
make_user_auth_headers,
102+
):
103+
organization = make_organization()
104+
105+
user = make_user(organization=organization)
106+
_, token = make_token_for_organization(organization)
107+
108+
team = make_team(organization)
109+
110+
user.teams.add(team)
111+
user.current_team = team
112+
user.save(update_fields=["current_team"])
113+
114+
alert_receive_channel = make_alert_receive_channel(organization)
115+
alert_group = make_alert_group(alert_receive_channel)
116+
117+
# escalation_chain = make_escalation_chain(organization)
118+
# schedule = make_schedule(organization, schedule_class=OnCallScheduleCalendar)
119+
# webhook = make_custom_action(organization)
120+
121+
for endpoint, instance in (
122+
("alertgroup", alert_group),
123+
# todo: implement team filtering for other resources
124+
# ("alert_receive_channel", alert_receive_channel),
125+
# ("escalation_chain", escalation_chain),
126+
# ("schedule", schedule),
127+
# ("custom_button", webhook),
128+
):
129+
client = APIClient()
130+
url = reverse(f"api-internal:{endpoint}-detail", kwargs={"pk": instance.public_primary_key})
131+
132+
response = client.get(url, **make_user_auth_headers(user, token))
133+
134+
assert response.status_code == status.HTTP_403_FORBIDDEN
135+
assert response.json() == {
136+
"error_code": "wrong_team",
137+
"owner_team": {"name": "General", "id": None, "email": None, "avatar_url": None},
138+
}
139+
140+
141+
@pytest.mark.django_db
142+
def test_team_permissions_wrong_team(
143+
make_organization,
144+
make_team,
145+
make_alert_group,
146+
make_alert_receive_channel,
147+
make_user,
148+
make_escalation_chain,
149+
make_schedule,
150+
make_custom_action,
151+
make_token_for_organization,
152+
make_user_auth_headers,
153+
):
154+
organization = make_organization()
155+
156+
user = make_user(organization=organization)
157+
_, token = make_token_for_organization(organization)
158+
159+
team = make_team(organization)
160+
user.teams.add(team)
161+
162+
alert_receive_channel = make_alert_receive_channel(organization, team=team)
163+
alert_group = make_alert_group(alert_receive_channel)
164+
165+
# escalation_chain = make_escalation_chain(organization, team=team)
166+
# schedule = make_schedule(organization, schedule_class=OnCallScheduleCalendar, team=team)
167+
# webhook = make_custom_action(organization, team=team)
168+
169+
for endpoint, instance in (
170+
("alertgroup", alert_group),
171+
# todo: implement team filtering for other resources
172+
# ("alert_receive_channel", alert_receive_channel),
173+
# ("escalation_chain", escalation_chain),
174+
# ("schedule", schedule),
175+
# ("custom_button", webhook),
176+
):
177+
client = APIClient()
178+
url = reverse(f"api-internal:{endpoint}-detail", kwargs={"pk": instance.public_primary_key})
179+
180+
response = client.get(url, **make_user_auth_headers(user, token))
181+
182+
assert response.status_code == status.HTTP_403_FORBIDDEN
183+
assert response.json() == {
184+
"error_code": "wrong_team",
185+
"owner_team": {
186+
"name": team.name,
187+
"id": team.public_primary_key,
188+
"email": team.email,
189+
"avatar_url": team.avatar_url,
190+
},
191+
}
192+
193+
194+
@pytest.mark.django_db
195+
def test_team_permissions_not_in_team(
196+
make_organization,
197+
make_team,
198+
make_alert_group,
199+
make_alert_receive_channel,
200+
make_user,
201+
make_escalation_chain,
202+
make_schedule,
203+
make_custom_action,
204+
make_token_for_organization,
205+
make_user_auth_headers,
206+
):
207+
organization = make_organization()
208+
209+
user = make_user(organization=organization)
210+
_, token = make_token_for_organization(organization)
211+
212+
team = make_team(organization)
213+
214+
alert_receive_channel = make_alert_receive_channel(organization, team=team)
215+
alert_group = make_alert_group(alert_receive_channel)
216+
217+
# escalation_chain = make_escalation_chain(organization, team=team)
218+
# schedule = make_schedule(organization, schedule_class=OnCallScheduleCalendar, team=team)
219+
# webhook = make_custom_action(organization, team=team)
220+
221+
for endpoint, instance in (
222+
("alertgroup", alert_group),
223+
# todo: implement team filtering for other resources
224+
# ("alert_receive_channel", alert_receive_channel),
225+
# ("escalation_chain", escalation_chain),
226+
# ("schedule", schedule),
227+
# ("custom_button", webhook),
228+
):
229+
client = APIClient()
230+
url = reverse(f"api-internal:{endpoint}-detail", kwargs={"pk": instance.public_primary_key})
231+
232+
response = client.get(url, **make_user_auth_headers(user, token))
233+
234+
assert response.status_code == status.HTTP_403_FORBIDDEN
235+
assert response.json() == {"error_code": "wrong_team"}
236+
237+
238+
@pytest.mark.django_db
239+
def test_team_permissions_right_team(
240+
make_organization,
241+
make_team,
242+
make_alert_group,
243+
make_alert_receive_channel,
244+
make_user,
245+
make_escalation_chain,
246+
make_schedule,
247+
make_custom_action,
248+
make_token_for_organization,
249+
make_user_auth_headers,
250+
):
251+
organization = make_organization()
252+
253+
user = make_user(organization=organization)
254+
_, token = make_token_for_organization(organization)
255+
256+
team = make_team(organization)
257+
258+
user.teams.add(team)
259+
user.current_team = team
260+
user.save(update_fields=["current_team"])
261+
262+
alert_receive_channel = make_alert_receive_channel(organization, team=team)
263+
alert_group = make_alert_group(alert_receive_channel)
264+
265+
# escalation_chain = make_escalation_chain(organization, team=team)
266+
# schedule = make_schedule(organization, schedule_class=OnCallScheduleCalendar, team=team)
267+
# webhook = make_custom_action(organization, team=team)
268+
269+
for endpoint, instance in (
270+
("alertgroup", alert_group),
271+
# todo: implement team filtering for other resources
272+
# ("alert_receive_channel", alert_receive_channel),
273+
# ("escalation_chain", escalation_chain),
274+
# ("schedule", schedule),
275+
# ("custom_button", webhook),
276+
):
277+
client = APIClient()
278+
url = reverse(f"api-internal:{endpoint}-detail", kwargs={"pk": instance.public_primary_key})
279+
280+
response = client.get(url, **make_user_auth_headers(user, token))
281+
282+
assert response.status_code == status.HTTP_200_OK

engine/apps/api/urls.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from django.conf import settings
2-
from django.urls import include, path
2+
from django.urls import include, path, re_path
33

44
from common.api_helpers.optional_slash_router import OptionalSlashRouter, optional_slash_path
55

66
from .views import UserNotificationPolicyView, auth
77
from .views.alert_group import AlertGroupView
88
from .views.alert_receive_channel import AlertReceiveChannelView
99
from .views.alert_receive_channel_template import AlertReceiveChannelTemplateView
10+
from .views.alerts import AlertDetailView
1011
from .views.apns_device import APNSDeviceAuthorizedViewSet
1112
from .views.channel_filter import ChannelFilterView
1213
from .views.custom_button import CustomButtonView
@@ -110,6 +111,7 @@
110111
),
111112
optional_slash_path("route_regex_debugger", RouteRegexDebuggerView.as_view(), name="route_regex_debugger"),
112113
optional_slash_path("insight_logs_test", TestInsightLogsAPIView.as_view(), name="insight-logs-test"),
114+
re_path(r"^alerts/(?P<id>\w+)/?$", AlertDetailView.as_view(), name="alerts-detail"),
113115
]
114116

115117
urlpatterns += [

engine/apps/api/views/alert_group.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from apps.user_management.models import User
1919
from common.api_helpers.exceptions import BadRequest
2020
from common.api_helpers.filters import DateRangeFilterMixin, ModelFieldFilterMixin
21-
from common.api_helpers.mixins import PreviewTemplateMixin, PublicPrimaryKeyMixin
21+
from common.api_helpers.mixins import PreviewTemplateMixin, PublicPrimaryKeyMixin, TeamFilteringMixin
2222
from common.api_helpers.paginators import TwentyFiveCursorPaginator
2323

2424

@@ -143,8 +143,13 @@ def filter_with_resolution_note(self, queryset, name, value):
143143
return queryset
144144

145145

146+
class AlertGroupTeamFilteringMixin(TeamFilteringMixin):
147+
TEAM_LOOKUP = "channel__team"
148+
149+
146150
class AlertGroupView(
147151
PreviewTemplateMixin,
152+
AlertGroupTeamFilteringMixin,
148153
PublicPrimaryKeyMixin,
149154
mixins.RetrieveModelMixin,
150155
mixins.ListModelMixin,

0 commit comments

Comments
 (0)