remove deprecated heartbeat_heartbeat table/model (#2534)
# What this PR does - Remove `heartbeat_heartbeat` table. This model/table does not seems to be deprecated/used anywhere (no data in this in production/staging; see more comments in the code about this).
This commit is contained in:
parent
aa4edad4a7
commit
63ac0972c5
14 changed files with 111 additions and 324 deletions
|
|
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove deprecated `heartbeat.HeartBeat` model/table by @joeyorlando ([#2534](https://github.com/grafana/oncall/pull/2534))
|
||||
|
||||
## v1.3.12 (2023-07-14)
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ To configure this feature as such:
|
|||
|
||||
1. Create a Webhook, or Formatted Webhook, Integration type.
|
||||
1. Under the "Heartbeat" tab in the Integration modal, copy the unique heartbeat URL that is shown.
|
||||
1. Set the hearbeat's expected time interval to 15 minutes (see note below regarding `ALERT_GROUP_ESCALATION_AUDITOR_CELERY_TASK_HEARTBEAT_INTERVAL`)
|
||||
1. Set the heartbeat's expected time interval to 15 minutes (see note below regarding `ALERT_GROUP_ESCALATION_AUDITOR_CELERY_TASK_HEARTBEAT_INTERVAL`)
|
||||
1. Configure the integration's escalation chain as necessary
|
||||
1. Populate the following env variables:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import HeartBeat
|
||||
|
||||
admin.site.register(HeartBeat)
|
||||
18
engine/apps/heartbeat/migrations/0002_delete_heartbeat.py
Normal file
18
engine/apps/heartbeat/migrations/0002_delete_heartbeat.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.20 on 2023-07-14 11:36
|
||||
|
||||
from django.db import migrations
|
||||
import django_migration_linter as linter
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('heartbeat', '0001_squashed_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
linter.IgnoreMigration(),
|
||||
migrations.DeleteModel(
|
||||
name='HeartBeat',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import logging
|
||||
import typing
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
|
|
@ -26,13 +27,19 @@ def generate_public_primary_key_for_integration_heart_beat():
|
|||
return new_public_primary_key
|
||||
|
||||
|
||||
class BaseHeartBeat(models.Model):
|
||||
"""
|
||||
Implements base heartbeat logic
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
class IntegrationHeartBeat(models.Model):
|
||||
TIMEOUT_CHOICES = (
|
||||
(60, "1 minute"),
|
||||
(120, "2 minutes"),
|
||||
(180, "3 minutes"),
|
||||
(300, "5 minutes"),
|
||||
(600, "10 minutes"),
|
||||
(900, "15 minutes"),
|
||||
(1800, "30 minutes"),
|
||||
(3600, "1 hour"),
|
||||
(43200, "12 hours"),
|
||||
(86400, "1 day"),
|
||||
)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
timeout_seconds = models.IntegerField(default=0)
|
||||
|
|
@ -41,8 +48,43 @@ class BaseHeartBeat(models.Model):
|
|||
actual_check_up_task_id = models.CharField(max_length=100)
|
||||
previous_alerted_state_was_life = models.BooleanField(default=True)
|
||||
|
||||
public_primary_key = models.CharField(
|
||||
max_length=20,
|
||||
validators=[MinLengthValidator(settings.PUBLIC_PRIMARY_KEY_MIN_LENGTH + 1)],
|
||||
unique=True,
|
||||
default=generate_public_primary_key_for_integration_heart_beat,
|
||||
)
|
||||
|
||||
alert_receive_channel = models.OneToOneField(
|
||||
"alerts.AlertReceiveChannel", on_delete=models.CASCADE, related_name="integration_heartbeat"
|
||||
)
|
||||
|
||||
@property
|
||||
def is_expired(self) -> bool:
|
||||
if self.last_heartbeat_time is None:
|
||||
# else heartbeat flow was not received, so heartbeat can't expire.
|
||||
return False
|
||||
|
||||
# if heartbeat signal was received check timeout
|
||||
return self.last_heartbeat_time + timezone.timedelta(seconds=self.timeout_seconds) < timezone.now()
|
||||
|
||||
@property
|
||||
def status(self) -> bool:
|
||||
"""
|
||||
Return bool indicates heartbeat status.
|
||||
True if first heartbeat signal was sent and flow is ok else False.
|
||||
If first heartbeat signal was not send it means that configuration was not finished and status not ok.
|
||||
"""
|
||||
if self.last_heartbeat_time is None:
|
||||
return False
|
||||
return not self.is_expired
|
||||
|
||||
@property
|
||||
def link(self) -> str:
|
||||
return urljoin(self.alert_receive_channel.integration_url, "heartbeat/")
|
||||
|
||||
@classmethod
|
||||
def perform_heartbeat_check(cls, heartbeat_id, task_request_id):
|
||||
def perform_heartbeat_check(cls, heartbeat_id: int, task_request_id: str) -> None:
|
||||
with transaction.atomic():
|
||||
heartbeats = cls.objects.filter(pk=heartbeat_id).select_for_update()
|
||||
if len(heartbeats) == 0:
|
||||
|
|
@ -54,7 +96,7 @@ class BaseHeartBeat(models.Model):
|
|||
else:
|
||||
logger.info(f"Heartbeat {heartbeat_id} is not actual {task_request_id}")
|
||||
|
||||
def check_heartbeat_state_and_save(self):
|
||||
def check_heartbeat_state_and_save(self) -> bool:
|
||||
"""
|
||||
Use this method if you want just check heartbeat status.
|
||||
"""
|
||||
|
|
@ -63,7 +105,7 @@ class BaseHeartBeat(models.Model):
|
|||
self.save(update_fields=["previous_alerted_state_was_life"])
|
||||
return state_changed
|
||||
|
||||
def check_heartbeat_state(self):
|
||||
def check_heartbeat_state(self) -> bool:
|
||||
"""
|
||||
Actually checking heartbeat.
|
||||
Use this method if you want to do changes of heartbeat instance while checking its status.
|
||||
|
|
@ -82,120 +124,7 @@ class BaseHeartBeat(models.Model):
|
|||
state_changed = True
|
||||
return state_changed
|
||||
|
||||
def on_heartbeat_restored(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def on_heartbeat_expired(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def is_expired(self):
|
||||
return self.last_heartbeat_time + timezone.timedelta(seconds=self.timeout_seconds) < timezone.now()
|
||||
|
||||
@property
|
||||
def expiration_time(self):
|
||||
return self.last_heartbeat_time + timezone.timedelta(seconds=self.timeout_seconds)
|
||||
|
||||
|
||||
class HeartBeat(BaseHeartBeat):
|
||||
"""
|
||||
HeartBeat Integration itself
|
||||
"""
|
||||
|
||||
alert_receive_channel = models.ForeignKey(
|
||||
"alerts.AlertReceiveChannel", on_delete=models.CASCADE, related_name="heartbeats"
|
||||
)
|
||||
|
||||
message = models.TextField(default="")
|
||||
title = models.TextField(default="HeartBeat Title")
|
||||
link = models.URLField(max_length=500, default=None, null=True)
|
||||
user_defined_id = models.CharField(default="default", max_length=100)
|
||||
|
||||
def on_heartbeat_restored(self):
|
||||
create_alert.apply_async(
|
||||
kwargs={
|
||||
"title": "[OK] " + self.title,
|
||||
"message": self.title,
|
||||
"image_url": None,
|
||||
"link_to_upstream_details": self.link,
|
||||
"alert_receive_channel_pk": self.alert_receive_channel.pk,
|
||||
"integration_unique_data": {},
|
||||
"raw_request_data": {
|
||||
"is_resolve": True,
|
||||
"id": self.pk,
|
||||
"user_defined_id": self.user_defined_id,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
def on_heartbeat_expired(self):
|
||||
create_alert.apply_async(
|
||||
kwargs={
|
||||
"title": "[EXPIRED] " + self.title,
|
||||
"message": self.message
|
||||
+ "\nCreated: {}\nExpires: {}\nLast HeartBeat: {}".format(
|
||||
self.created_at,
|
||||
self.expiration_time,
|
||||
self.last_checkup_task_time,
|
||||
),
|
||||
"image_url": None,
|
||||
"link_to_upstream_details": self.link,
|
||||
"alert_receive_channel_pk": self.alert_receive_channel.pk,
|
||||
"integration_unique_data": {},
|
||||
"raw_request_data": {
|
||||
"is_resolve": False,
|
||||
"id": self.pk,
|
||||
"user_defined_id": self.user_defined_id,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = (("alert_receive_channel", "user_defined_id"),)
|
||||
|
||||
|
||||
class IntegrationHeartBeat(BaseHeartBeat):
|
||||
"""
|
||||
HeartBeat for Integration (FormattedWebhook, Grafana, etc.)
|
||||
"""
|
||||
|
||||
public_primary_key = models.CharField(
|
||||
max_length=20,
|
||||
validators=[MinLengthValidator(settings.PUBLIC_PRIMARY_KEY_MIN_LENGTH + 1)],
|
||||
unique=True,
|
||||
default=generate_public_primary_key_for_integration_heart_beat,
|
||||
)
|
||||
|
||||
alert_receive_channel = models.OneToOneField(
|
||||
"alerts.AlertReceiveChannel", on_delete=models.CASCADE, related_name="integration_heartbeat"
|
||||
)
|
||||
|
||||
@property
|
||||
def is_expired(self):
|
||||
if self.last_heartbeat_time is not None:
|
||||
# if heartbeat signal was received check timeout
|
||||
return self.last_heartbeat_time + timezone.timedelta(seconds=self.timeout_seconds) < timezone.now()
|
||||
else:
|
||||
# else heartbeat flow was not received, so heartbeat can't expire.
|
||||
return False
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""
|
||||
Return bool indicates heartbeat status.
|
||||
True if first heartbeat signal was sent and flow is ok else False.
|
||||
If first heartbeat signal was not send it means that configuration was not finished and status not ok.
|
||||
"""
|
||||
if self.last_heartbeat_time is not None:
|
||||
return not self.is_expired
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def link(self):
|
||||
return urljoin(self.alert_receive_channel.integration_url, "heartbeat/")
|
||||
|
||||
def on_heartbeat_restored(self):
|
||||
def on_heartbeat_restored(self) -> None:
|
||||
create_alert.apply_async(
|
||||
kwargs={
|
||||
"title": self.alert_receive_channel.heartbeat_restored_title,
|
||||
|
|
@ -208,7 +137,7 @@ class IntegrationHeartBeat(BaseHeartBeat):
|
|||
},
|
||||
)
|
||||
|
||||
def on_heartbeat_expired(self):
|
||||
def on_heartbeat_expired(self) -> None:
|
||||
create_alert.apply_async(
|
||||
kwargs={
|
||||
"title": self.alert_receive_channel.heartbeat_expired_title,
|
||||
|
|
@ -221,36 +150,23 @@ class IntegrationHeartBeat(BaseHeartBeat):
|
|||
},
|
||||
)
|
||||
|
||||
TIMEOUT_CHOICES = (
|
||||
(60, "1 minute"),
|
||||
(120, "2 minutes"),
|
||||
(180, "3 minutes"),
|
||||
(300, "5 minutes"),
|
||||
(600, "10 minutes"),
|
||||
(900, "15 minutes"),
|
||||
(1800, "30 minutes"),
|
||||
(3600, "1 hour"),
|
||||
(43200, "12 hours"),
|
||||
(86400, "1 day"),
|
||||
)
|
||||
|
||||
# Insight logs
|
||||
@property
|
||||
def insight_logs_type_verbal(self):
|
||||
def insight_logs_type_verbal(self) -> str:
|
||||
return "integration_heartbeat"
|
||||
|
||||
@property
|
||||
def insight_logs_verbal(self):
|
||||
def insight_logs_verbal(self) -> str:
|
||||
return f"Integration Heartbeat for {self.alert_receive_channel.insight_logs_verbal}"
|
||||
|
||||
@property
|
||||
def insight_logs_serialized(self):
|
||||
def insight_logs_serialized(self) -> typing.Dict[str, str | int]:
|
||||
return {
|
||||
"timeout": self.timeout_seconds,
|
||||
}
|
||||
|
||||
@property
|
||||
def insight_logs_metadata(self):
|
||||
def insight_logs_metadata(self) -> typing.Dict[str, str]:
|
||||
return {
|
||||
"integration": self.alert_receive_channel.insight_logs_verbal,
|
||||
"integration_id": self.alert_receive_channel.public_primary_key,
|
||||
|
|
|
|||
|
|
@ -10,36 +10,12 @@ from common.custom_celery_tasks import shared_dedicated_queue_retry_task
|
|||
logger = get_task_logger(__name__)
|
||||
|
||||
|
||||
@shared_dedicated_queue_retry_task(bind=True)
|
||||
def heartbeat_checkup(self, heartbeat_id):
|
||||
HeartBeat = apps.get_model("heartbeat", "HeartBeat")
|
||||
HeartBeat.perform_heartbeat_check(heartbeat_id, heartbeat_checkup.request.id)
|
||||
|
||||
|
||||
@shared_dedicated_queue_retry_task()
|
||||
def integration_heartbeat_checkup(heartbeat_id):
|
||||
def integration_heartbeat_checkup(heartbeat_id: int) -> None:
|
||||
IntegrationHeartBeat = apps.get_model("heartbeat", "IntegrationHeartBeat")
|
||||
IntegrationHeartBeat.perform_heartbeat_check(heartbeat_id, integration_heartbeat_checkup.request.id)
|
||||
|
||||
|
||||
@shared_dedicated_queue_retry_task()
|
||||
def restore_heartbeat_tasks():
|
||||
"""
|
||||
Restore heartbeat tasks in case they got lost for some reason
|
||||
"""
|
||||
HeartBeat = apps.get_model("heartbeat", "HeartBeat")
|
||||
for heartbeat in HeartBeat.objects.all():
|
||||
if (
|
||||
heartbeat.last_checkup_task_time
|
||||
+ timezone.timedelta(minutes=5)
|
||||
+ timezone.timedelta(seconds=heartbeat.timeout_seconds)
|
||||
< timezone.now()
|
||||
):
|
||||
task = heartbeat_checkup.apply_async((heartbeat.pk,), countdown=5)
|
||||
heartbeat.actual_check_up_task_id = task.id
|
||||
heartbeat.save()
|
||||
|
||||
|
||||
@shared_dedicated_queue_retry_task()
|
||||
def process_heartbeat_task(alert_receive_channel_pk):
|
||||
start = perf_counter()
|
||||
|
|
|
|||
|
|
@ -34,10 +34,10 @@ class HeartBeatTextCreator:
|
|||
def _get_heartbeat_expired_message(self):
|
||||
heartbeat_docs_url = create_engine_url("/#/integrations/heartbeat", override_base=settings.DOCS_URL)
|
||||
heartbeat_expired_message = (
|
||||
f"Amixr was waiting for a heartbeat from {self.integration_verbal}. "
|
||||
f"Heartbeat is missing. That could happen because {self.integration_verbal} stopped or"
|
||||
f" there are connectivity issues between Amixr and {self.integration_verbal}. "
|
||||
f"Read more in Amixr docs: {heartbeat_docs_url}"
|
||||
f"Grafana OnCall was waiting for a heartbeat from {self.integration_verbal} "
|
||||
f"and one was not received. This can happen when {self.integration_verbal} has stopped or "
|
||||
f"there are connectivity issues between Grafana OnCall and {self.integration_verbal}. "
|
||||
f"You can read more in the Grafana OnCall docs here: {heartbeat_docs_url}"
|
||||
)
|
||||
return heartbeat_expired_message
|
||||
|
||||
|
|
@ -46,7 +46,9 @@ class HeartBeatTextCreator:
|
|||
return heartbeat_expired_title
|
||||
|
||||
def _get_heartbeat_restored_message(self):
|
||||
heartbeat_expired_message = f"Amixr received a signal from {self.integration_verbal}. Heartbeat restored."
|
||||
heartbeat_expired_message = (
|
||||
f"Grafana OnCall received a signal from {self.integration_verbal}. Heartbeat has been restored."
|
||||
)
|
||||
return heartbeat_expired_message
|
||||
|
||||
def _get_heartbeat_instruction_template(self):
|
||||
|
|
@ -59,9 +61,9 @@ class HeartBeatTextCreatorForTitleGrouping(HeartBeatTextCreator):
|
|||
"""
|
||||
|
||||
def _get_heartbeat_expired_title(self):
|
||||
heartbeat_expired_title = "Amixr heartbeat"
|
||||
heartbeat_expired_title = "Grafana OnCall heartbeat"
|
||||
return heartbeat_expired_title
|
||||
|
||||
def _get_heartbeat_restored_title(self):
|
||||
heartbeat_expired_title = "Amixr heartbeat"
|
||||
heartbeat_expired_title = "Grafana OnCall heartbeat"
|
||||
return heartbeat_expired_title
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ from .views import (
|
|||
AmazonSNS,
|
||||
GrafanaAlertingAPIView,
|
||||
GrafanaAPIView,
|
||||
HeartBeatAPIView,
|
||||
IntegrationHeartBeatAPIView,
|
||||
UniversalAPIView,
|
||||
)
|
||||
|
|
@ -33,7 +32,6 @@ urlpatterns = [
|
|||
path("grafana_alerting/<str:alert_channel_key>/", GrafanaAlertingAPIView.as_view(), name="grafana_alerting"),
|
||||
path("alertmanager/<str:alert_channel_key>/", AlertManagerAPIView.as_view(), name="alertmanager"),
|
||||
path("amazon_sns/<str:alert_channel_key>/", AmazonSNS.as_view(), name="amazon_sns"),
|
||||
path("heartbeat/<str:alert_channel_key>/", HeartBeatAPIView.as_view(), name="heartbeat"),
|
||||
path("alertmanager_v2/<str:alert_channel_key>/", AlertManagerV2View.as_view(), name="alertmanager_v2"),
|
||||
path("<str:integration_type>/<str:alert_channel_key>/", UniversalAPIView.as_view(), name="universal"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
import json
|
||||
import logging
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.db.utils import IntegrityError
|
||||
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
|
||||
from django.template import loader
|
||||
from django.utils import timezone
|
||||
from django.http import HttpResponseBadRequest, JsonResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django_sns_view.views import SNSEndpoint
|
||||
|
|
@ -16,7 +11,7 @@ from rest_framework.response import Response
|
|||
from rest_framework.views import APIView
|
||||
|
||||
from apps.alerts.models import AlertReceiveChannel
|
||||
from apps.heartbeat.tasks import heartbeat_checkup, process_heartbeat_task
|
||||
from apps.heartbeat.tasks import process_heartbeat_task
|
||||
from apps.integrations.mixins import (
|
||||
AlertChannelDefiningMixin,
|
||||
BrowsableInstructionMixin,
|
||||
|
|
@ -262,122 +257,6 @@ class UniversalAPIView(BrowsableInstructionMixin, AlertChannelDefiningMixin, Int
|
|||
return Response("Ok.")
|
||||
|
||||
|
||||
# TODO: restore HeartBeatAPIView integration or clean it up as it is not used now
|
||||
class HeartBeatAPIView(AlertChannelDefiningMixin, APIView):
|
||||
def get(self, request):
|
||||
template = loader.get_template("heartbeat_link.html")
|
||||
docs_url = create_engine_url("/#/integrations/heartbeat", override_base=settings.DOCS_URL)
|
||||
return HttpResponse(
|
||||
template.render(
|
||||
{
|
||||
"docs_url": docs_url,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def post(self, request):
|
||||
alert_receive_channel = self.request.alert_receive_channel
|
||||
HeartBeat = apps.get_model("heartbeat", "HeartBeat")
|
||||
|
||||
if request.data.get("action") == "activate":
|
||||
# timeout_seconds
|
||||
timeout_seconds = request.data.get("timeout_seconds")
|
||||
try:
|
||||
timeout_seconds = int(timeout_seconds)
|
||||
except ValueError:
|
||||
timeout_seconds = None
|
||||
|
||||
if timeout_seconds is None:
|
||||
return Response(status=400, data="timeout_seconds int expected")
|
||||
# id
|
||||
_id = request.data.get("id", "default")
|
||||
# title
|
||||
title = request.data.get("title", "Title")
|
||||
# title
|
||||
link = request.data.get("link")
|
||||
# message
|
||||
message = request.data.get("message")
|
||||
|
||||
heartbeat = HeartBeat(
|
||||
alert_receive_channel=alert_receive_channel,
|
||||
timeout_seconds=timeout_seconds,
|
||||
title=title,
|
||||
message=message,
|
||||
link=link,
|
||||
user_defined_id=_id,
|
||||
last_heartbeat_time=timezone.now(),
|
||||
last_checkup_task_time=timezone.now(),
|
||||
actual_check_up_task_id="none",
|
||||
)
|
||||
try:
|
||||
heartbeat.save()
|
||||
with transaction.atomic():
|
||||
heartbeat = HeartBeat.objects.filter(pk=heartbeat.pk).select_for_update()[0]
|
||||
task = heartbeat_checkup.apply_async(
|
||||
(heartbeat.pk,),
|
||||
countdown=heartbeat.timeout_seconds,
|
||||
)
|
||||
heartbeat.actual_check_up_task_id = task.id
|
||||
heartbeat.save()
|
||||
except IntegrityError:
|
||||
return Response(status=400, data="id should be unique")
|
||||
|
||||
elif request.data.get("action") == "deactivate":
|
||||
_id = request.data.get("id", "default")
|
||||
try:
|
||||
heartbeat = HeartBeat.objects.filter(
|
||||
alert_receive_channel=alert_receive_channel,
|
||||
user_defined_id=_id,
|
||||
).get()
|
||||
heartbeat.delete()
|
||||
except HeartBeat.DoesNotExist:
|
||||
return Response(status=400, data="heartbeat not found")
|
||||
|
||||
elif request.data.get("action") == "list":
|
||||
result = []
|
||||
heartbeats = HeartBeat.objects.filter(
|
||||
alert_receive_channel=alert_receive_channel,
|
||||
).all()
|
||||
for heartbeat in heartbeats:
|
||||
result.append(
|
||||
{
|
||||
"created_at": heartbeat.created_at,
|
||||
"last_heartbeat": heartbeat.last_heartbeat_time,
|
||||
"expiration_time": heartbeat.expiration_time,
|
||||
"is_expired": heartbeat.is_expired,
|
||||
"id": heartbeat.user_defined_id,
|
||||
"title": heartbeat.title,
|
||||
"timeout_seconds": heartbeat.timeout_seconds,
|
||||
"link": heartbeat.link,
|
||||
"message": heartbeat.message,
|
||||
}
|
||||
)
|
||||
return Response(result)
|
||||
|
||||
elif request.data.get("action") == "heartbeat":
|
||||
_id = request.data.get("id", "default")
|
||||
with transaction.atomic():
|
||||
try:
|
||||
heartbeat = HeartBeat.objects.filter(
|
||||
alert_receive_channel=alert_receive_channel,
|
||||
user_defined_id=_id,
|
||||
).select_for_update()[0]
|
||||
task = heartbeat_checkup.apply_async(
|
||||
(heartbeat.pk,),
|
||||
countdown=heartbeat.timeout_seconds,
|
||||
)
|
||||
heartbeat.actual_check_up_task_id = task.id
|
||||
heartbeat.last_heartbeat_time = timezone.now()
|
||||
update_fields = ["actual_check_up_task_id", "last_heartbeat_time"]
|
||||
state_changed = heartbeat.check_heartbeat_state()
|
||||
if state_changed:
|
||||
update_fields.append("previous_alerted_state_was_life")
|
||||
heartbeat.save(update_fields=update_fields)
|
||||
except IndexError:
|
||||
return Response(status=400, data="heartbeat not found")
|
||||
return Response("Ok.")
|
||||
|
||||
|
||||
class IntegrationHeartBeatAPIView(AlertChannelDefiningMixin, IntegrationHeartBeatRateLimitMixin, APIView):
|
||||
def get(self, request):
|
||||
self._process_heartbeat_signal(request, request.alert_receive_channel)
|
||||
|
|
|
|||
|
|
@ -415,11 +415,6 @@ ALERT_GROUP_ESCALATION_AUDITOR_CELERY_TASK_HEARTBEAT_URL = os.getenv(
|
|||
)
|
||||
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
"restore_heartbeat_tasks": {
|
||||
"task": "apps.heartbeat.tasks.restore_heartbeat_tasks",
|
||||
"schedule": 10 * 60,
|
||||
"args": (),
|
||||
},
|
||||
"start_refresh_ical_final_schedules": {
|
||||
"task": "apps.schedules.tasks.refresh_ical_files.start_refresh_ical_final_schedules",
|
||||
"schedule": crontab(minute=15, hour=0),
|
||||
|
|
|
|||
|
|
@ -51,10 +51,8 @@ CELERY_TASK_ROUTES = {
|
|||
"apps.alerts.tasks.delete_alert_group.delete_alert_group": {"queue": "default"},
|
||||
"apps.alerts.tasks.send_alert_group_signal.send_alert_group_signal": {"queue": "default"},
|
||||
"apps.alerts.tasks.wipe.wipe": {"queue": "default"},
|
||||
"apps.heartbeat.tasks.heartbeat_checkup": {"queue": "default"},
|
||||
"apps.heartbeat.tasks.integration_heartbeat_checkup": {"queue": "default"},
|
||||
"apps.heartbeat.tasks.process_heartbeat_task": {"queue": "default"},
|
||||
"apps.heartbeat.tasks.restore_heartbeat_tasks": {"queue": "default"},
|
||||
"apps.metrics_exporter.tasks.start_calculate_and_cache_metrics": {"queue": "default"},
|
||||
"apps.metrics_exporter.tasks.start_recalculation_for_new_metric": {"queue": "default"},
|
||||
"apps.metrics_exporter.tasks.save_organizations_ids_in_cache": {"queue": "default"},
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ import { UserActions } from 'utils/authorization';
|
|||
|
||||
const cx = cn.bind({});
|
||||
|
||||
interface IntegrationHearbeatFormProps {
|
||||
interface IntegrationHeartbeatFormProps {
|
||||
alertReceveChannelId: AlertReceiveChannel['id'];
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
const IntegrationHearbeatForm = observer(({ alertReceveChannelId, onClose }: IntegrationHearbeatFormProps) => {
|
||||
const IntegrationHeartbeatForm = observer(({ alertReceveChannelId, onClose }: IntegrationHeartbeatFormProps) => {
|
||||
const [interval, setInterval] = useState<number>(undefined);
|
||||
|
||||
const { heartbeatStore, alertReceiveChannelStore } = useStore();
|
||||
|
|
@ -110,6 +110,6 @@ const IntegrationHearbeatForm = observer(({ alertReceveChannelId, onClose }: Int
|
|||
}
|
||||
});
|
||||
|
||||
export default withMobXProviderContext(IntegrationHearbeatForm) as ({
|
||||
export default withMobXProviderContext(IntegrationHeartbeatForm) as ({
|
||||
alertReceveChannelId,
|
||||
}: IntegrationHearbeatFormProps) => JSX.Element;
|
||||
}: IntegrationHeartbeatFormProps) => JSX.Element;
|
||||
|
|
@ -43,7 +43,7 @@ import { WithContextMenu } from 'components/WithContextMenu/WithContextMenu';
|
|||
import EditRegexpRouteTemplateModal from 'containers/EditRegexpRouteTemplateModal/EditRegexpRouteTemplateModal';
|
||||
import CollapsedIntegrationRouteDisplay from 'containers/IntegrationContainers/CollapsedIntegrationRouteDisplay/CollapsedIntegrationRouteDisplay';
|
||||
import ExpandedIntegrationRouteDisplay from 'containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay';
|
||||
import IntegrationHeartbeatForm from 'containers/IntegrationContainers/IntegrationHearbeatForm/IntegrationHeartbeatForm';
|
||||
import IntegrationHeartbeatForm from 'containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm';
|
||||
import IntegrationTemplateList from 'containers/IntegrationContainers/IntegrationTemplatesList';
|
||||
import IntegrationForm from 'containers/IntegrationForm/IntegrationForm';
|
||||
import IntegrationTemplate from 'containers/IntegrationTemplate/IntegrationTemplate';
|
||||
|
|
@ -742,7 +742,7 @@ const IntegrationActions: React.FC<IntegrationActionsProps> = ({
|
|||
}>(undefined);
|
||||
|
||||
const [isIntegrationSettingsOpen, setIsIntegrationSettingsOpen] = useState(false);
|
||||
const [isHearbeatFormOpen, setIsHearbeatFormOpen] = useState(false);
|
||||
const [isHeartbeatFormOpen, setIsHeartbeatFormOpen] = useState(false);
|
||||
const [isDemoModalOpen, setIsDemoModalOpen] = useState(false);
|
||||
const [maintenanceData, setMaintenanceData] = useState<{
|
||||
disabled: boolean;
|
||||
|
|
@ -784,10 +784,10 @@ const IntegrationActions: React.FC<IntegrationActionsProps> = ({
|
|||
/>
|
||||
)}
|
||||
|
||||
{isHearbeatFormOpen && (
|
||||
{isHeartbeatFormOpen && (
|
||||
<IntegrationHeartbeatForm
|
||||
alertReceveChannelId={alertReceiveChannel['id']}
|
||||
onClose={() => setIsHearbeatFormOpen(false)}
|
||||
onClose={() => setIsHeartbeatFormOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
@ -823,7 +823,7 @@ const IntegrationActions: React.FC<IntegrationActionsProps> = ({
|
|||
|
||||
{showHeartbeatSettings() && (
|
||||
<WithPermissionControlTooltip key="ok" userAction={UserActions.IntegrationsWrite}>
|
||||
<div className={cx('integration__actionItem')} onClick={() => setIsHearbeatFormOpen(true)}>
|
||||
<div className={cx('integration__actionItem')} onClick={() => setIsHeartbeatFormOpen(true)}>
|
||||
Heartbeat Settings
|
||||
</div>
|
||||
</WithPermissionControlTooltip>
|
||||
|
|
@ -1117,7 +1117,7 @@ const IntegrationHeader: React.FC<IntegrationHeaderProps> = ({
|
|||
/>
|
||||
)}
|
||||
|
||||
{renderHearbeat(alertReceiveChannel)}
|
||||
{renderHeartbeat(alertReceiveChannel)}
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'row', gap: '16px', marginLeft: '8px' }}>
|
||||
<div className={cx('headerTop__item')}>
|
||||
|
|
@ -1153,7 +1153,7 @@ const IntegrationHeader: React.FC<IntegrationHeaderProps> = ({
|
|||
);
|
||||
}
|
||||
|
||||
function renderHearbeat(alertReceiveChannel: AlertReceiveChannel) {
|
||||
function renderHeartbeat(alertReceiveChannel: AlertReceiveChannel) {
|
||||
const heartbeatId = alertReceiveChannelStore.alertReceiveChannelToHeartbeat[alertReceiveChannel.id];
|
||||
const heartbeat = heartbeatStore.items[heartbeatId];
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ import RemoteFilters from 'containers/RemoteFilters/RemoteFilters';
|
|||
import TeamName from 'containers/TeamName/TeamName';
|
||||
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
|
||||
import { HeartIcon, HeartRedIcon } from 'icons';
|
||||
import { AlertReceiveChannelStore } from 'models/alert_receive_channel/alert_receive_channel';
|
||||
import { AlertReceiveChannel, MaintenanceMode } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import { HeartbeatStore } from 'models/heartbeat/heartbeat';
|
||||
import IntegrationHelper from 'pages/integration/Integration.helper';
|
||||
import { PageProps, WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
|
|
@ -343,7 +345,11 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
);
|
||||
}
|
||||
|
||||
renderHeartbeat(item: AlertReceiveChannel, alertReceiveChannelStore, heartbeatStore) {
|
||||
renderHeartbeat(
|
||||
item: AlertReceiveChannel,
|
||||
alertReceiveChannelStore: AlertReceiveChannelStore,
|
||||
heartbeatStore: HeartbeatStore
|
||||
) {
|
||||
const alertReceiveChannel = alertReceiveChannelStore.items[item.id];
|
||||
|
||||
const heartbeatId = alertReceiveChannelStore.alertReceiveChannelToHeartbeat[alertReceiveChannel.id];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue