Skip to content

Commit ec11b31

Browse files
author
Joey Orlando
authored
v1.10.4
2 parents d4e4bc9 + 8f55a9e commit ec11b31

File tree

33 files changed

+315
-119
lines changed

33 files changed

+315
-119
lines changed

engine/apps/alerts/models/alert.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length
2626

2727
if typing.TYPE_CHECKING:
28-
from django.db.models.manager import RelatedManager
29-
3028
from apps.alerts.models import AlertGroup, AlertReceiveChannel, ChannelFilter
3129

3230
logger = logging.getLogger(__name__)
@@ -49,7 +47,6 @@ def generate_public_primary_key_for_alert():
4947

5048
class Alert(models.Model):
5149
group: typing.Optional["AlertGroup"]
52-
resolved_alert_groups: "RelatedManager['AlertGroup']"
5350

5451
public_primary_key = models.CharField(
5552
max_length=20,
@@ -163,11 +160,6 @@ def create(
163160
if not group.resolved and mark_as_resolved:
164161
group.resolve_by_source()
165162

166-
# Store exact alert which resolved group.
167-
if group.resolved_by == AlertGroup.SOURCE and group.resolved_by_alert is None:
168-
group.resolved_by_alert = alert
169-
group.save(update_fields=["resolved_by_alert"])
170-
171163
if group_created:
172164
# all code below related to maintenance mode
173165
maintenance_uuid = None

engine/apps/alerts/models/alert_group.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import urllib
55
from collections import namedtuple
66
from functools import partial
7-
from urllib.parse import urljoin
87

98
from celery import uuid as celery_uuid
109
from django.conf import settings
@@ -13,6 +12,7 @@
1312
from django.db.models import JSONField, Q, QuerySet
1413
from django.utils import timezone
1514
from django.utils.functional import cached_property
15+
from django_deprecate_fields import deprecate_field
1616

1717
from apps.alerts.constants import ActionSource, AlertGroupState
1818
from apps.alerts.escalation_snapshot import EscalationSnapshotMixin
@@ -27,10 +27,10 @@
2727
send_alert_group_signal_for_delete,
2828
unsilence_task,
2929
)
30+
from apps.grafana_plugin.ui_url_builder import UIURLBuilder
3031
from apps.metrics_exporter.tasks import update_metrics_for_alert_group
3132
from apps.slack.slack_formatter import SlackFormatter
3233
from apps.user_management.models import User
33-
from common.constants.plugin_ids import PluginID
3434
from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length
3535
from common.utils import clean_markup, str_or_backup
3636

@@ -201,7 +201,6 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
201201
personal_log_records: "RelatedManager['UserNotificationPolicyLogRecord']"
202202
resolution_notes: "RelatedManager['ResolutionNote']"
203203
resolution_note_slack_messages: "RelatedManager['ResolutionNoteSlackMessage']"
204-
resolved_by_alert: typing.Optional["Alert"]
205204
resolved_by_user: typing.Optional["User"]
206205
root_alert_group: typing.Optional["AlertGroup"]
207206
silenced_by_user: typing.Optional["User"]
@@ -289,12 +288,16 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
289288
related_name="resolved_alert_groups",
290289
)
291290

292-
resolved_by_alert = models.ForeignKey(
293-
"alerts.Alert",
294-
on_delete=models.SET_NULL,
295-
null=True,
296-
default=None,
297-
related_name="resolved_alert_groups",
291+
# NOTE: see https://raintank-corp.slack.com/archives/C07RGREUH4Z/p1728494111646319
292+
# This field should eventually be dropped as it's no longer being set/read anywhere
293+
resolved_by_alert = deprecate_field(
294+
models.ForeignKey(
295+
"alerts.Alert",
296+
on_delete=models.SET_NULL,
297+
null=True,
298+
default=None,
299+
related_name="resolved_alert_groups",
300+
)
298301
)
299302

300303
resolved_at = models.DateTimeField(blank=True, null=True)
@@ -543,17 +546,19 @@ def permalinks(self) -> Permalinks:
543546

544547
@property
545548
def web_link(self) -> str:
546-
return urljoin(self.channel.organization.web_link, f"alert-groups/{self.public_primary_key}")
549+
return UIURLBuilder(self.channel.organization).alert_group_detail(self.public_primary_key)
547550

548551
@property
549552
def declare_incident_link(self) -> str:
550-
"""Generate a link for AlertGroup to declare Grafana Incident by click"""
551-
incident_link = urljoin(self.channel.organization.grafana_url, f"a/{PluginID.INCIDENT}/incidents/declare/")
553+
"""
554+
Generate a link for AlertGroup to declare Grafana Incident by click
555+
"""
552556
caption = urllib.parse.quote_plus("OnCall Alert Group")
553557
title = urllib.parse.quote_plus(self.web_title_cache) if self.web_title_cache else DEFAULT_BACKUP_TITLE
554558
title = title[:2000] # set max title length to avoid exceptions with too long declare incident link
555559
link = urllib.parse.quote_plus(self.web_link)
556-
return urljoin(incident_link, f"?caption={caption}&url={link}&title={title}")
560+
561+
return UIURLBuilder(self.channel.organization).declare_incident(f"?caption={caption}&url={link}&title={title}")
557562

558563
@property
559564
def happened_while_maintenance(self):

engine/apps/alerts/models/alert_receive_channel.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import logging
22
import typing
33
from functools import cached_property
4-
from urllib.parse import urljoin
54

65
import emoji
76
from celery import uuid as celery_uuid
@@ -21,6 +20,7 @@
2120
from apps.alerts.tasks import disable_maintenance, disconnect_integration_from_alerting_contact_points
2221
from apps.base.messaging import get_messaging_backend_from_id
2322
from apps.base.utils import live_settings
23+
from apps.grafana_plugin.ui_url_builder import UIURLBuilder
2424
from apps.integrations.legacy_prefix import remove_legacy_prefix
2525
from apps.integrations.metadata import heartbeat
2626
from apps.integrations.tasks import create_alert, create_alertmanager_alerts
@@ -422,8 +422,8 @@ def emojized_verbal_name(self):
422422

423423
@property
424424
def new_incidents_web_link(self):
425-
return urljoin(
426-
self.organization.web_link, f"?page=incidents&integration={self.public_primary_key}&status=0&p=1"
425+
return UIURLBuilder(self.organization).alert_groups(
426+
f"?integration={self.public_primary_key}&status={AlertGroup.NEW}",
427427
)
428428

429429
@property
@@ -531,8 +531,8 @@ def created_name(self):
531531
return f"{self.get_integration_display()} {self.smile_code}"
532532

533533
@property
534-
def web_link(self):
535-
return urljoin(self.organization.web_link, f"integrations/{self.public_primary_key}")
534+
def web_link(self) -> str:
535+
return UIURLBuilder(self.organization).integration_detail(self.public_primary_key)
536536

537537
@property
538538
def integration_url(self) -> str | None:

engine/apps/api/serializers/user.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def get_cloud_connection_status(self, obj: User) -> CloudSyncStatus | None:
181181
connector = self.context.get("connector", None)
182182
identities = self.context.get("cloud_identities", {})
183183
identity = identities.get(obj.email, None)
184-
status, _ = cloud_user_identity_status(connector, identity)
184+
status, _ = cloud_user_identity_status(obj.organization, connector, identity)
185185
return status
186186
return None
187187

engine/apps/api/tests/test_auth.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@
1010

1111
from apps.auth_token.constants import SLACK_AUTH_TOKEN_NAME
1212
from apps.social_auth.backends import SLACK_INSTALLATION_BACKEND
13+
from common.constants.plugin_ids import PluginID
1314
from common.constants.slack_auth import SLACK_OAUTH_ACCESS_RESPONSE
1415

16+
GRAFANA_URL = "http://example.com"
17+
1518

1619
@pytest.mark.django_db
1720
@pytest.mark.parametrize(
1821
"backend_name,expected_url",
1922
(
20-
("slack-login", "/a/grafana-oncall-app/users/me"),
21-
(SLACK_INSTALLATION_BACKEND, "/a/grafana-oncall-app/chat-ops"),
23+
("slack-login", f"{GRAFANA_URL}/a/{PluginID.ONCALL}/users/me"),
24+
(SLACK_INSTALLATION_BACKEND, f"{GRAFANA_URL}/a/{PluginID.ONCALL}/chat-ops"),
2225
),
2326
)
2427
def test_complete_slack_auth_redirect_ok(
@@ -28,7 +31,7 @@ def test_complete_slack_auth_redirect_ok(
2831
backend_name,
2932
expected_url,
3033
):
31-
organization = make_organization()
34+
organization = make_organization(grafana_url=GRAFANA_URL)
3235
admin = make_user_for_organization(organization)
3336
_, slack_token = make_slack_token_for_user(admin)
3437

@@ -181,7 +184,7 @@ def test_google_complete_auth_redirect_ok(
181184
make_user_for_organization,
182185
make_google_oauth2_token_for_user,
183186
):
184-
organization = make_organization()
187+
organization = make_organization(grafana_url=GRAFANA_URL)
185188
admin = make_user_for_organization(organization)
186189
_, google_oauth2_token = make_google_oauth2_token_for_user(admin)
187190

@@ -194,7 +197,7 @@ def test_google_complete_auth_redirect_ok(
194197
response = client.get(url)
195198

196199
assert response.status_code == status.HTTP_302_FOUND
197-
assert response.url == "/a/grafana-oncall-app/users/me"
200+
assert response.url == f"{GRAFANA_URL}/a/{PluginID.ONCALL}/users/me"
198201

199202

200203
@pytest.mark.django_db

engine/apps/api/views/auth.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
from urllib.parse import urljoin
32

43
from django.conf import settings
54
from django.contrib.auth import REDIRECT_FIELD_NAME
@@ -19,6 +18,7 @@
1918
get_installation_link_from_chatops_proxy,
2019
get_slack_oauth_response_from_chatops_proxy,
2120
)
21+
from apps.grafana_plugin.ui_url_builder import UIURLBuilder
2222
from apps.slack.installation import install_slack_integration
2323
from apps.social_auth.backends import SLACK_INSTALLATION_BACKEND, LoginSlackOAuth2V2
2424

@@ -73,13 +73,6 @@ def overridden_login_social_auth(request: Request, backend: str) -> Response:
7373
@psa("social:complete")
7474
def overridden_complete_social_auth(request: Request, backend: str, *args, **kwargs) -> Response:
7575
"""Authentication complete view"""
76-
if isinstance(request.backend, (LoginSlackOAuth2V2, GoogleOAuth2)):
77-
# if this was a user login/linking account, redirect to profile
78-
redirect_to = "/a/grafana-oncall-app/users/me"
79-
else:
80-
# InstallSlackOAuth2V2 backend
81-
redirect_to = "/a/grafana-oncall-app/chat-ops"
82-
8376
kwargs.update(
8477
user=request.user,
8578
redirect_name=REDIRECT_FIELD_NAME,
@@ -99,8 +92,16 @@ def overridden_complete_social_auth(request: Request, backend: str, *args, **kwa
9992
return_to = request.backend.strategy.session.get(REDIRECT_FIELD_NAME)
10093

10194
if return_to is None:
102-
# We build the frontend url using org url since multiple stacks could be connected to one backend.
103-
return_to = urljoin(request.user.organization.grafana_url, redirect_to)
95+
url_builder = UIURLBuilder(request.user.organization)
96+
97+
# if this was a user login/linking account, redirect to profile (ie. users/me)
98+
# otherwise it pertains to the InstallSlackOAuth2V2 backend, and we should redirect to the chat-ops page
99+
return_to = (
100+
url_builder.user_profile()
101+
if isinstance(request.backend, (LoginSlackOAuth2V2, GoogleOAuth2))
102+
else url_builder.chatops()
103+
)
104+
104105
return HttpResponseRedirect(return_to)
105106

106107

engine/apps/chatops_proxy/register_oncall_tenant.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
# register_oncall_tenant moved to separate file from engine/apps/chatops_proxy/utils.py to avoid circular imports.
2+
import typing
3+
24
from django.conf import settings
35

46
from apps.chatops_proxy.client import APP_TYPE_ONCALL, ChatopsProxyAPIClient
57

8+
if typing.TYPE_CHECKING:
9+
from apps.user_management.models import Organization
10+
611

7-
def register_oncall_tenant(org):
12+
def register_oncall_tenant(org: "Organization") -> None:
813
"""
914
register_oncall_tenant registers oncall organization as a tenant in chatops-proxy.
1015
"""

engine/apps/chatops_proxy/utils.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
"""
44
import logging
55
import typing
6-
from urllib.parse import urljoin
76

87
from django.conf import settings
98

9+
from apps.grafana_plugin.ui_url_builder import UIURLBuilder
10+
1011
from .client import APP_TYPE_ONCALL, PROVIDER_TYPE_SLACK, ChatopsProxyAPIClient, ChatopsProxyAPIException
1112
from .register_oncall_tenant import register_oncall_tenant
1213
from .tasks import (
@@ -16,10 +17,13 @@
1617
unregister_oncall_tenant_async,
1718
)
1819

20+
if typing.TYPE_CHECKING:
21+
from apps.user_management.models import Organization, User
22+
1923
logger = logging.getLogger(__name__)
2024

2125

22-
def get_installation_link_from_chatops_proxy(user) -> typing.Optional[str]:
26+
def get_installation_link_from_chatops_proxy(user: "User") -> typing.Optional[str]:
2327
"""
2428
get_installation_link_from_chatops_proxy fetches slack installation link from chatops proxy.
2529
If there is no existing slack installation - if returns link, If slack already installed, it returns None.
@@ -30,7 +34,7 @@ def get_installation_link_from_chatops_proxy(user) -> typing.Optional[str]:
3034
link, _ = client.get_slack_oauth_link(
3135
org.stack_id,
3236
user.user_id,
33-
urljoin(org.web_link, "settings?tab=ChatOps&chatOpsTab=Slack"),
37+
UIURLBuilder(org).settings("?tab=ChatOps&chatOpsTab=Slack"),
3438
APP_TYPE_ONCALL,
3539
)
3640
return link
@@ -44,13 +48,13 @@ def get_installation_link_from_chatops_proxy(user) -> typing.Optional[str]:
4448
raise api_exc
4549

4650

47-
def get_slack_oauth_response_from_chatops_proxy(stack_id) -> dict:
51+
def get_slack_oauth_response_from_chatops_proxy(stack_id: int) -> dict:
4852
client = ChatopsProxyAPIClient(settings.ONCALL_GATEWAY_URL, settings.ONCALL_GATEWAY_API_TOKEN)
4953
slack_installation, _ = client.get_oauth_installation(stack_id, PROVIDER_TYPE_SLACK)
5054
return slack_installation.oauth_response
5155

5256

53-
def register_oncall_tenant_with_async_fallback(org):
57+
def register_oncall_tenant_with_async_fallback(org: "Organization") -> None:
5458
"""
5559
register_oncall_tenant tries to register oncall tenant synchronously and fall back to task in case of any exceptions
5660
to make sure that tenant is registered.

0 commit comments

Comments
 (0)