From 71c2bd596d43e6f85a812759f46299accbc77d57 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Fri, 14 Jul 2023 13:44:26 +0200 Subject: [PATCH 1/4] WIP --- engine/apps/heartbeat/admin.py | 5 - .../migrations/0002_delete_heartbeat.py | 18 ++ engine/apps/heartbeat/models.py | 200 +++++------------- engine/apps/heartbeat/tasks.py | 26 +-- .../heartbeat/_heartbeat_text_creator.py | 16 +- engine/apps/integrations/urls.py | 2 - engine/apps/integrations/views.py | 125 +---------- engine/settings/base.py | 5 - engine/settings/prod_without_db.py | 2 - .../src/pages/integration/Integration.tsx | 17 +- .../src/pages/integrations/Integrations.tsx | 8 +- 11 files changed, 105 insertions(+), 319 deletions(-) delete mode 100644 engine/apps/heartbeat/admin.py create mode 100644 engine/apps/heartbeat/migrations/0002_delete_heartbeat.py diff --git a/engine/apps/heartbeat/admin.py b/engine/apps/heartbeat/admin.py deleted file mode 100644 index 601aace698..0000000000 --- a/engine/apps/heartbeat/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.contrib import admin - -from .models import HeartBeat - -admin.site.register(HeartBeat) diff --git a/engine/apps/heartbeat/migrations/0002_delete_heartbeat.py b/engine/apps/heartbeat/migrations/0002_delete_heartbeat.py new file mode 100644 index 0000000000..f00195aae7 --- /dev/null +++ b/engine/apps/heartbeat/migrations/0002_delete_heartbeat.py @@ -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', + ), + ] diff --git a/engine/apps/heartbeat/models.py b/engine/apps/heartbeat/models.py index 4f9019dc4f..eb9b9cd627 100644 --- a/engine/apps/heartbeat/models.py +++ b/engine/apps/heartbeat/models.py @@ -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 Meta: 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 @@ def perform_heartbeat_check(cls, heartbeat_id, task_request_id): 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 @@ def check_heartbeat_state_and_save(self): 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 @@ def check_heartbeat_state(self): 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 @@ def on_heartbeat_restored(self): }, ) - 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 @@ def on_heartbeat_expired(self): }, ) - 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, diff --git a/engine/apps/heartbeat/tasks.py b/engine/apps/heartbeat/tasks.py index 55b92fce1c..269676be53 100644 --- a/engine/apps/heartbeat/tasks.py +++ b/engine/apps/heartbeat/tasks.py @@ -10,36 +10,12 @@ 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() diff --git a/engine/apps/integrations/metadata/heartbeat/_heartbeat_text_creator.py b/engine/apps/integrations/metadata/heartbeat/_heartbeat_text_creator.py index 859957a16c..78fc8b0352 100644 --- a/engine/apps/integrations/metadata/heartbeat/_heartbeat_text_creator.py +++ b/engine/apps/integrations/metadata/heartbeat/_heartbeat_text_creator.py @@ -34,10 +34,10 @@ def _get_heartbeat_expired_title(self): 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 @@ def _get_heartbeat_restored_title(self): 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 diff --git a/engine/apps/integrations/urls.py b/engine/apps/integrations/urls.py index a27b404629..8ce4c87887 100644 --- a/engine/apps/integrations/urls.py +++ b/engine/apps/integrations/urls.py @@ -12,7 +12,6 @@ AmazonSNS, GrafanaAlertingAPIView, GrafanaAPIView, - HeartBeatAPIView, IntegrationHeartBeatAPIView, UniversalAPIView, ) @@ -33,7 +32,6 @@ path("grafana_alerting//", GrafanaAlertingAPIView.as_view(), name="grafana_alerting"), path("alertmanager//", AlertManagerAPIView.as_view(), name="alertmanager"), path("amazon_sns//", AmazonSNS.as_view(), name="amazon_sns"), - path("heartbeat//", HeartBeatAPIView.as_view(), name="heartbeat"), path("alertmanager_v2//", AlertManagerV2View.as_view(), name="alertmanager_v2"), path("//", UniversalAPIView.as_view(), name="universal"), ] diff --git a/engine/apps/integrations/views.py b/engine/apps/integrations/views.py index b1e49056bd..67b26883d5 100644 --- a/engine/apps/integrations/views.py +++ b/engine/apps/integrations/views.py @@ -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.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 @@ def post(self, request, *args, **kwargs): 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) diff --git a/engine/settings/base.py b/engine/settings/base.py index e9a58b1f05..7a7b62284a 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -415,11 +415,6 @@ class BrokerTypes: ) 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), diff --git a/engine/settings/prod_without_db.py b/engine/settings/prod_without_db.py index eeb67edc02..0e1b018895 100644 --- a/engine/settings/prod_without_db.py +++ b/engine/settings/prod_without_db.py @@ -51,10 +51,8 @@ def on_uwsgi_worker_exit(): "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"}, diff --git a/grafana-plugin/src/pages/integration/Integration.tsx b/grafana-plugin/src/pages/integration/Integration.tsx index 8046589684..a315162ddb 100644 --- a/grafana-plugin/src/pages/integration/Integration.tsx +++ b/grafana-plugin/src/pages/integration/Integration.tsx @@ -751,6 +751,11 @@ const IntegrationActions: React.FC = ({ const { id } = alertReceiveChannel; + const heartbeatId = alertReceiveChannelStore.alertReceiveChannelToHeartbeat[id]; + const heartbeat = heartbeatStore.items[heartbeatId]; + + console.log(heartbeat); + return ( <> {confirmModal && ( @@ -821,13 +826,11 @@ const IntegrationActions: React.FC = ({ Integration Settings - {showHeartbeatSettings() && ( - -
setIsHearbeatFormOpen(true)}> - Heartbeat Settings -
-
- )} + +
setIsHearbeatFormOpen(true)}> + {heartbeat ? 'Heartbeat Settings' : 'Create Heartbeat'} +
+
{!alertReceiveChannel.maintenance_till && ( diff --git a/grafana-plugin/src/pages/integrations/Integrations.tsx b/grafana-plugin/src/pages/integrations/Integrations.tsx index f85a145605..8799b3b8be 100644 --- a/grafana-plugin/src/pages/integrations/Integrations.tsx +++ b/grafana-plugin/src/pages/integrations/Integrations.tsx @@ -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 ); } - renderHeartbeat(item: AlertReceiveChannel, alertReceiveChannelStore, heartbeatStore) { + renderHeartbeat( + item: AlertReceiveChannel, + alertReceiveChannelStore: AlertReceiveChannelStore, + heartbeatStore: HeartbeatStore + ) { const alertReceiveChannel = alertReceiveChannelStore.items[item.id]; const heartbeatId = alertReceiveChannelStore.alertReceiveChannelToHeartbeat[alertReceiveChannel.id]; From dddaff897828898758d1187c3dc751b18fcdb06f Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Fri, 14 Jul 2023 14:16:21 +0200 Subject: [PATCH 2/4] wip --- docs/sources/open-source/_index.md | 2 +- .../IntegrationHeartbeatForm.tsx | 8 ++++---- .../src/pages/integration/Integration.tsx | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) rename grafana-plugin/src/containers/IntegrationContainers/{IntegrationHearbeatForm => IntegrationHeartbeatForm}/IntegrationHeartbeatForm.tsx (94%) diff --git a/docs/sources/open-source/_index.md b/docs/sources/open-source/_index.md index 674f5d78c8..c42b817435 100644 --- a/docs/sources/open-source/_index.md +++ b/docs/sources/open-source/_index.md @@ -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: diff --git a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHearbeatForm/IntegrationHeartbeatForm.tsx b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx similarity index 94% rename from grafana-plugin/src/containers/IntegrationContainers/IntegrationHearbeatForm/IntegrationHeartbeatForm.tsx rename to grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx index 654a603f08..f8efcd88ab 100644 --- a/grafana-plugin/src/containers/IntegrationContainers/IntegrationHearbeatForm/IntegrationHeartbeatForm.tsx +++ b/grafana-plugin/src/containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm.tsx @@ -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(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; diff --git a/grafana-plugin/src/pages/integration/Integration.tsx b/grafana-plugin/src/pages/integration/Integration.tsx index a315162ddb..be67954894 100644 --- a/grafana-plugin/src/pages/integration/Integration.tsx +++ b/grafana-plugin/src/pages/integration/Integration.tsx @@ -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 = ({ }>(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; @@ -789,10 +789,10 @@ const IntegrationActions: React.FC = ({ /> )} - {isHearbeatFormOpen && ( + {isHeartbeatFormOpen && ( setIsHearbeatFormOpen(false)} + onClose={() => setIsHeartbeatFormOpen(false)} /> )} @@ -827,7 +827,7 @@ const IntegrationActions: React.FC = ({ -
setIsHearbeatFormOpen(true)}> +
setIsHeartbeatFormOpen(true)}> {heartbeat ? 'Heartbeat Settings' : 'Create Heartbeat'}
@@ -1120,7 +1120,7 @@ const IntegrationHeader: React.FC = ({ /> )} - {renderHearbeat(alertReceiveChannel)} + {renderHeartbeat(alertReceiveChannel)}
@@ -1156,7 +1156,7 @@ const IntegrationHeader: React.FC = ({ ); } - function renderHearbeat(alertReceiveChannel: AlertReceiveChannel) { + function renderHeartbeat(alertReceiveChannel: AlertReceiveChannel) { const heartbeatId = alertReceiveChannelStore.alertReceiveChannelToHeartbeat[alertReceiveChannel.id]; const heartbeat = heartbeatStore.items[heartbeatId]; From 7969606410885e63f2a8ffa8211b7d36c4f95525 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Fri, 14 Jul 2023 14:47:02 +0200 Subject: [PATCH 3/4] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7516ef610f..20a660e740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 From 0f19cb6e3ea1a19cf7744d9ad816571bc9722a06 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Sat, 15 Jul 2023 16:24:30 +0200 Subject: [PATCH 4/4] last fixes --- .../src/pages/integration/Integration.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/grafana-plugin/src/pages/integration/Integration.tsx b/grafana-plugin/src/pages/integration/Integration.tsx index be67954894..51b52057a6 100644 --- a/grafana-plugin/src/pages/integration/Integration.tsx +++ b/grafana-plugin/src/pages/integration/Integration.tsx @@ -751,11 +751,6 @@ const IntegrationActions: React.FC = ({ const { id } = alertReceiveChannel; - const heartbeatId = alertReceiveChannelStore.alertReceiveChannelToHeartbeat[id]; - const heartbeat = heartbeatStore.items[heartbeatId]; - - console.log(heartbeat); - return ( <> {confirmModal && ( @@ -826,11 +821,13 @@ const IntegrationActions: React.FC = ({ Integration Settings
- -
setIsHeartbeatFormOpen(true)}> - {heartbeat ? 'Heartbeat Settings' : 'Create Heartbeat'} -
-
+ {showHeartbeatSettings() && ( + +
setIsHeartbeatFormOpen(true)}> + Heartbeat Settings +
+
+ )} {!alertReceiveChannel.maintenance_till && (