From 94934bf3e49a409869437aca605d5eb8bc28246a Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Wed, 26 Oct 2022 15:47:08 +0200 Subject: [PATCH] Remove migration-tool Django app and UI page (#708) * remove migration_tool app (#687) * remove migration-tool page (#699) --- engine/apps/migration_tool/__init__.py | 0 engine/apps/migration_tool/constants.py | 7 - .../migrations/0001_squashed_initial.py | 33 - ...2_amixrmigrationtaskstatus_organization.py | 22 - .../migration_tool/migrations/__init__.py | 0 engine/apps/migration_tool/models/__init__.py | 2 - .../models/amixr_migration_task_status.py | 27 - .../migration_tool/models/locked_alert.py | 5 - engine/apps/migration_tool/tasks.py | 612 ------------------ engine/apps/migration_tool/urls.py | 12 - engine/apps/migration_tool/utils.py | 35 - engine/apps/migration_tool/views/__init__.py | 0 .../views/customers_migration_tool.py | 186 ------ engine/engine/urls.py | 1 - engine/settings/base.py | 1 - grafana-plugin/src/pages/index.ts | 8 - .../migration-tool/MigrationTool.module.css | 9 - .../pages/migration-tool/MigrationTool.tsx | 364 ----------- .../pages/migration-tool/img/api-tokens.png | Bin 46193 -> 0 bytes 19 files changed, 1324 deletions(-) delete mode 100644 engine/apps/migration_tool/__init__.py delete mode 100644 engine/apps/migration_tool/constants.py delete mode 100644 engine/apps/migration_tool/migrations/0001_squashed_initial.py delete mode 100644 engine/apps/migration_tool/migrations/0002_amixrmigrationtaskstatus_organization.py delete mode 100644 engine/apps/migration_tool/migrations/__init__.py delete mode 100644 engine/apps/migration_tool/models/__init__.py delete mode 100644 engine/apps/migration_tool/models/amixr_migration_task_status.py delete mode 100644 engine/apps/migration_tool/models/locked_alert.py delete mode 100644 engine/apps/migration_tool/tasks.py delete mode 100644 engine/apps/migration_tool/urls.py delete mode 100644 engine/apps/migration_tool/utils.py delete mode 100644 engine/apps/migration_tool/views/__init__.py delete mode 100644 engine/apps/migration_tool/views/customers_migration_tool.py delete mode 100644 grafana-plugin/src/pages/migration-tool/MigrationTool.module.css delete mode 100644 grafana-plugin/src/pages/migration-tool/MigrationTool.tsx delete mode 100644 grafana-plugin/src/pages/migration-tool/img/api-tokens.png diff --git a/engine/apps/migration_tool/__init__.py b/engine/apps/migration_tool/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/engine/apps/migration_tool/constants.py b/engine/apps/migration_tool/constants.py deleted file mode 100644 index 4ab4377d..00000000 --- a/engine/apps/migration_tool/constants.py +++ /dev/null @@ -1,7 +0,0 @@ -# amixr api url -REQUEST_URL = "https://amixr.io/api/v1" - -# migration status -NOT_STARTED = "not_started" -IN_PROGRESS = "in_progress" -FINISHED = "finished" diff --git a/engine/apps/migration_tool/migrations/0001_squashed_initial.py b/engine/apps/migration_tool/migrations/0001_squashed_initial.py deleted file mode 100644 index 3772704d..00000000 --- a/engine/apps/migration_tool/migrations/0001_squashed_initial.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 3.2.5 on 2022-05-31 14:46 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('alerts', '0001_squashed_initial'), - ] - - operations = [ - migrations.CreateModel( - name='AmixrMigrationTaskStatus', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('task_id', models.CharField(db_index=True, max_length=500)), - ('name', models.CharField(max_length=500)), - ('started_at', models.DateTimeField(auto_now_add=True)), - ('is_finished', models.BooleanField(default=False)), - ], - ), - migrations.CreateModel( - name='LockedAlert', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('alert', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='migrator_lock', to='alerts.alert')), - ], - ), - ] diff --git a/engine/apps/migration_tool/migrations/0002_amixrmigrationtaskstatus_organization.py b/engine/apps/migration_tool/migrations/0002_amixrmigrationtaskstatus_organization.py deleted file mode 100644 index 8480acdd..00000000 --- a/engine/apps/migration_tool/migrations/0002_amixrmigrationtaskstatus_organization.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.5 on 2022-05-31 14:46 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('user_management', '0001_squashed_initial'), - ('migration_tool', '0001_squashed_initial'), - ] - - operations = [ - migrations.AddField( - model_name='amixrmigrationtaskstatus', - name='organization', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='migration_tasks', to='user_management.organization'), - ), - ] diff --git a/engine/apps/migration_tool/migrations/__init__.py b/engine/apps/migration_tool/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/engine/apps/migration_tool/models/__init__.py b/engine/apps/migration_tool/models/__init__.py deleted file mode 100644 index c7478eab..00000000 --- a/engine/apps/migration_tool/models/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .amixr_migration_task_status import AmixrMigrationTaskStatus # noqa: F401 -from .locked_alert import LockedAlert # noqa: F401 diff --git a/engine/apps/migration_tool/models/amixr_migration_task_status.py b/engine/apps/migration_tool/models/amixr_migration_task_status.py deleted file mode 100644 index 543013d7..00000000 --- a/engine/apps/migration_tool/models/amixr_migration_task_status.py +++ /dev/null @@ -1,27 +0,0 @@ -from celery import uuid as celery_uuid -from django.db import models - - -class AmixrMigrationTaskStatusQuerySet(models.QuerySet): - def get_migration_task_id(self, organization_id, name): - migrate_schedules_task_id = celery_uuid() - self.model(organization_id=organization_id, name=name, task_id=migrate_schedules_task_id).save() - return migrate_schedules_task_id - - -class AmixrMigrationTaskStatus(models.Model): - objects = AmixrMigrationTaskStatusQuerySet.as_manager() - - task_id = models.CharField(max_length=500, db_index=True) - name = models.CharField(max_length=500) - organization = models.ForeignKey( - to="user_management.Organization", - related_name="migration_tasks", - on_delete=models.deletion.CASCADE, - ) - started_at = models.DateTimeField(auto_now_add=True) - is_finished = models.BooleanField(default=False) - - def update_status_to_finished(self): - self.is_finished = True - self.save(update_fields=["is_finished"]) diff --git a/engine/apps/migration_tool/models/locked_alert.py b/engine/apps/migration_tool/models/locked_alert.py deleted file mode 100644 index 8771c6ce..00000000 --- a/engine/apps/migration_tool/models/locked_alert.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.db import models - - -class LockedAlert(models.Model): - alert = models.OneToOneField("alerts.Alert", on_delete=models.CASCADE, related_name="migrator_lock") diff --git a/engine/apps/migration_tool/tasks.py b/engine/apps/migration_tool/tasks.py deleted file mode 100644 index 23ceff21..00000000 --- a/engine/apps/migration_tool/tasks.py +++ /dev/null @@ -1,612 +0,0 @@ -import logging - -from celery.utils.log import get_task_logger -from django.apps import apps -from django.conf import settings -from django.db import transaction -from django.utils import timezone -from rest_framework import exceptions - -from apps.alerts.models import Alert, AlertGroup, AlertReceiveChannel, ResolutionNote -from apps.migration_tool.models import AmixrMigrationTaskStatus, LockedAlert -from apps.migration_tool.utils import convert_string_to_datetime, get_data_with_respect_to_pagination -from apps.public_api.serializers import PersonalNotificationRuleSerializer -from common.custom_celery_tasks import shared_dedicated_queue_retry_task - -logger = get_task_logger(__name__) -logger.setLevel(logging.DEBUG) - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=0 if settings.DEBUG else None -) -def start_migration_from_old_amixr(api_token, organization_id, user_id): - logger.info(f"Start migration task from amixr for organization {organization_id}") - users = get_users(organization_id, api_token) - - migrate_schedules_task_id = AmixrMigrationTaskStatus.objects.get_migration_task_id( - organization_id=organization_id, name=migrate_schedules.name - ) - migrate_schedules.apply_async( - (api_token, organization_id, user_id, users), - task_id=migrate_schedules_task_id, - countdown=5, - ) - - start_migration_user_data_task_id = AmixrMigrationTaskStatus.objects.get_migration_task_id( - organization_id=organization_id, name=start_migration_user_data.name - ) - start_migration_user_data.apply_async( - (api_token, organization_id, users), - task_id=start_migration_user_data_task_id, - ) - logger.info(f"Start 'start_migration_from_old_amixr' task for organization {organization_id}") - - -def get_users(organization_id, api_token): - Organization = apps.get_model("user_management", "Organization") - organization = Organization.objects.get(pk=organization_id) - # get all users from old amixr - old_users = get_data_with_respect_to_pagination(api_token, "users") - old_users_emails = [old_user["email"] for old_user in old_users] - # find users in Grafana OnCall by email - grafana_users = organization.users.filter(email__in=old_users_emails).values("email", "id") - - grafana_users_dict = { - gu["email"]: { - "id": gu["id"], - } - for gu in grafana_users - } - - users = {} - for old_user in old_users: - if old_user["email"] in grafana_users_dict: - users[old_user["id"]] = grafana_users_dict[old_user["email"]] - users[old_user["id"]]["old_verified_phone_number"] = old_user.get("verified_phone_number") - users[old_user["id"]]["old_public_primary_key"] = old_user["id"] - - # Example result: - # users = { - # "OLD_PUBLIC_PK": { - # "id": 1, # user pk in OnCall db - # "old_verified_phone_number": "1234", - # "old_public_primary_key": "OLD_PUBLIC_PK", - # }, - # ... - # } - return users - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=0 if settings.DEBUG else None -) -def migrate_schedules(api_token, organization_id, user_id, users): - logger.info(f"Started migration schedules for organization {organization_id}") - OnCallScheduleICal = apps.get_model("schedules", "OnCallScheduleICal") - Organization = apps.get_model("user_management", "Organization") - organization = Organization.objects.get(pk=organization_id) - - schedules = get_data_with_respect_to_pagination(api_token, "schedules") - existing_schedules_names = set(organization.oncall_schedules.values_list("name", flat=True)) - created_schedules = {} - for schedule in schedules: - if not schedule["ical_url"] or schedule["name"] in existing_schedules_names: - continue - - new_schedule = OnCallScheduleICal( - organization=organization, - name=schedule["name"], - ical_url_primary=schedule["ical_url"], - team_id=None, - ) - - new_schedule.save() - - created_schedules[schedule["id"]] = { - "id": new_schedule.pk, - } - # Example result: - # created_schedules = { - # "OLD_PUBLIC_PK": { - # "id": 1, # schedule pk in OnCall db - # }, - # ... - # } - - migrate_integrations_task_id = AmixrMigrationTaskStatus.objects.get_migration_task_id( - organization_id=organization_id, name=migrate_integrations.name - ) - migrate_integrations.apply_async( - (api_token, organization_id, user_id, created_schedules, users), task_id=migrate_integrations_task_id - ) - - current_task_id = migrate_schedules.request.id - AmixrMigrationTaskStatus.objects.get(task_id=current_task_id).update_status_to_finished() - logger.info(f"Finished migration schedules for organization {organization_id}") - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=0 if settings.DEBUG else None -) -def migrate_integrations(api_token, organization_id, user_id, created_schedules, users): - logger.info(f"Started migration integrations for organization {organization_id}") - Organization = apps.get_model("user_management", "Organization") - organization = Organization.objects.get(pk=organization_id) - - integrations = get_data_with_respect_to_pagination(api_token, "integrations") - - existing_integrations_names = set(organization.alert_receive_channels.values_list("verbal_name", flat=True)) - - for integration in integrations: - if integration["name"] in existing_integrations_names: - continue - - try: - integration_type = [ - key - for key, value in AlertReceiveChannel.INTEGRATIONS_TO_REVERSE_URL_MAP.items() - if value == integration["type"] - ][0] - except IndexError: - continue - if integration_type not in AlertReceiveChannel.WEB_INTEGRATION_CHOICES: - continue - - new_integration = AlertReceiveChannel.create( - organization=organization, - verbal_name=integration["name"], - integration=integration_type, - author_id=user_id, - slack_title_template=integration["templates"]["slack"]["title"], - slack_message_template=integration["templates"]["slack"]["message"], - slack_image_url_template=integration["templates"]["slack"]["image_url"], - sms_title_template=integration["templates"]["sms"]["title"], - phone_call_title_template=integration["templates"]["phone_call"]["title"], - web_title_template=integration["templates"]["web"]["title"], - web_message_template=integration["templates"]["web"]["message"], - web_image_url_template=integration["templates"]["web"]["image_url"], - email_title_template=integration["templates"]["email"]["title"], - email_message_template=integration["templates"]["email"]["message"], - telegram_title_template=integration["templates"]["telegram"]["title"], - telegram_message_template=integration["templates"]["telegram"]["message"], - telegram_image_url_template=integration["templates"]["telegram"]["image_url"], - grouping_id_template=integration["templates"]["grouping_key"], - resolve_condition_template=integration["templates"]["resolve_signal"], - acknowledge_condition_template=integration["templates"]["acknowledge_signal"], - ) - # collect integration data in a dict - integration_data = { - "id": new_integration.pk, - "verbal_name": new_integration.verbal_name, - "old_public_primary_key": integration["id"], - } - - migrate_routes_task_id = AmixrMigrationTaskStatus.objects.get_migration_task_id( - organization_id=organization_id, name=migrate_routes.name - ) - migrate_routes.apply_async( - (api_token, organization_id, users, created_schedules, integration_data), - task_id=migrate_routes_task_id, - countdown=3, - ) - - current_task_id = migrate_integrations.request.id - AmixrMigrationTaskStatus.objects.get(task_id=current_task_id).update_status_to_finished() - logger.info(f"Finished migration integrations for organization {organization_id}") - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=0 if settings.DEBUG else None -) -def migrate_routes(api_token, organization_id, users, created_schedules, integration_data): - logger.info(f"Start migration routes for organization {organization_id}") - AlertReceiveChannel = apps.get_model("alerts", "AlertReceiveChannel") - ChannelFilter = apps.get_model("alerts", "ChannelFilter") - Organization = apps.get_model("user_management", "Organization") - organization = Organization.objects.get(pk=organization_id) - - integration = AlertReceiveChannel.objects.filter(pk=integration_data["id"]).first() - if integration: - url = "routes?integration_id={}".format(integration_data["old_public_primary_key"]) - routes = get_data_with_respect_to_pagination(api_token, url) - - default_route = integration.channel_filters.get(is_default=True) - existing_chain_names = set(organization.escalation_chains.values_list("name", flat=True)) - existing_route_filtering_term = set(integration.channel_filters.values_list("filtering_term", flat=True)) - - for route in routes: - is_default_route = route["is_the_last_route"] - filtering_term = route["routing_regex"] - - if is_default_route: - escalation_chain_name = f"{integration_data['verbal_name'][:90]} - default" - else: - if filtering_term in existing_route_filtering_term: - continue - escalation_chain_name = f"{integration_data['verbal_name']} - {filtering_term}"[:100] - - if escalation_chain_name in existing_chain_names: - escalation_chain = organization.escalation_chains.get(name=escalation_chain_name) - else: - escalation_chain = organization.escalation_chains.create(name=escalation_chain_name) - - if is_default_route: - new_route = default_route - new_route.escalation_chain = escalation_chain - new_route.save(update_fields=["escalation_chain"]) - else: - new_route = ChannelFilter( - alert_receive_channel_id=integration_data["id"], - escalation_chain_id=escalation_chain.pk, - filtering_term=filtering_term, - order=route["position"], - ) - new_route.save() - - route_data = { - "id": new_route.pk, - "old_public_primary_key": route["id"], - "escalation_chain": { - "id": escalation_chain.pk, - }, - } - - migrate_escalation_policies_task_id = AmixrMigrationTaskStatus.objects.get_migration_task_id( - organization_id=organization_id, name=migrate_escalation_policies.name - ) - migrate_escalation_policies.apply_async( - (api_token, organization_id, users, created_schedules, route_data), - task_id=migrate_escalation_policies_task_id, - countdown=2, - ) - - start_migration_alert_groups_task_id = AmixrMigrationTaskStatus.objects.get_migration_task_id( - organization_id=organization_id, name=start_migration_alert_groups.name - ) - start_migration_alert_groups.apply_async( - (api_token, organization_id, users, integration_data, route_data), - task_id=start_migration_alert_groups_task_id, - countdown=10, - ) - - current_task_id = migrate_routes.request.id - AmixrMigrationTaskStatus.objects.get(task_id=current_task_id).update_status_to_finished() - logger.info(f"Finished migration routes for organization {organization_id}") - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=0 if settings.DEBUG else None -) -def migrate_escalation_policies(api_token, organization_id, users, created_schedules, route_data): - logger.info(f"Start migration escalation policies for organization {organization_id}") - EscalationChain = apps.get_model("alerts", "EscalationChain") - EscalationPolicy = apps.get_model("alerts", "EscalationPolicy") - - escalation_chain = EscalationChain.objects.filter(pk=route_data["escalation_chain"]["id"]).first() - if escalation_chain and not escalation_chain.escalation_policies.exists(): - - url = "escalation_policies?route_id={}".format(route_data["old_public_primary_key"]) - escalation_policies = get_data_with_respect_to_pagination(api_token, url) - - for escalation_policy in escalation_policies: - try: - step_type = [ - key - for key, value in EscalationPolicy.PUBLIC_STEP_CHOICES_MAP.items() - if value == escalation_policy["type"] and key in EscalationPolicy.PUBLIC_STEP_CHOICES - ][0] - except IndexError: - continue - - if step_type in EscalationPolicy.DEFAULT_TO_IMPORTANT_STEP_MAPPING and escalation_policy.get("important"): - step_type = EscalationPolicy.DEFAULT_TO_IMPORTANT_STEP_MAPPING[step_type] - - notify_to_users_queue = [] - - if step_type == EscalationPolicy.STEP_NOTIFY_USERS_QUEUE: - notify_to_users_queue = [ - users[user_old_public_pk]["id"] - for user_old_public_pk in escalation_policy.get("persons_to_notify_next_each_time", []) - if user_old_public_pk in users - ] - elif step_type in [ - EscalationPolicy.STEP_NOTIFY_MULTIPLE_USERS, - EscalationPolicy.STEP_NOTIFY_MULTIPLE_USERS_IMPORTANT, - ]: - notify_to_users_queue = [ - users[user_old_public_pk]["id"] - for user_old_public_pk in escalation_policy.get("persons_to_notify", []) - if user_old_public_pk in users - ] - - if step_type == EscalationPolicy.STEP_NOTIFY_IF_TIME: - notify_from_time = timezone.datetime.strptime( - escalation_policy.get("notify_if_time_from"), "%H:%M:%SZ" - ).time() - notify_to_time = timezone.datetime.strptime( - escalation_policy.get("notify_if_time_to"), "%H:%M:%SZ" - ).time() - else: - notify_from_time, notify_to_time = None, None - duration = escalation_policy.get("duration") - wait_delay = timezone.timedelta(seconds=duration) if duration else None - - schedule_id = escalation_policy.get("notify_on_call_from_schedule") - - notify_schedule_id = created_schedules.get(schedule_id, {}).get("id") if schedule_id else None - - new_escalation_policy = EscalationPolicy( - step=step_type, - order=escalation_policy["position"], - escalation_chain=escalation_chain, - notify_schedule_id=notify_schedule_id, - wait_delay=wait_delay, - from_time=notify_from_time, - to_time=notify_to_time, - ) - - new_escalation_policy.save() - if notify_to_users_queue: - new_escalation_policy.notify_to_users_queue.set(notify_to_users_queue) - - current_task_id = migrate_escalation_policies.request.id - AmixrMigrationTaskStatus.objects.get(task_id=current_task_id).update_status_to_finished() - logger.info(f"Finished migration escalation policies for organization {organization_id}") - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=0 if settings.DEBUG else None -) -def start_migration_alert_groups(api_token, organization_id, users, integration_data, route_data): - logger.info(f"Start migration alert groups for organization {organization_id}") - ChannelFilter = apps.get_model("alerts", "ChannelFilter") - - url = "incidents?route_id={}".format(route_data["old_public_primary_key"]) - alert_groups = get_data_with_respect_to_pagination(api_token, url) - - route = ChannelFilter.objects.filter(pk=route_data["id"]).first() - - if route and not route.alert_groups.exists(): - for alert_group in alert_groups: - - migrate_alert_group_task_id = AmixrMigrationTaskStatus.objects.get_migration_task_id( - organization_id=organization_id, name=migrate_alert_group.name - ) - migrate_alert_group.apply_async( - (api_token, organization_id, users, integration_data, route_data, alert_group), - task_id=migrate_alert_group_task_id, - ) - - current_task_id = start_migration_alert_groups.request.id - AmixrMigrationTaskStatus.objects.get(task_id=current_task_id).update_status_to_finished() - logger.info(f"Finished 'start_migration_alert_groups' for organization {organization_id}") - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=0 if settings.DEBUG else None -) -def migrate_alert_group(api_token, organization_id, users, integration_data, route_data, alert_group_to_migrate): - logger.info(f"Start migration alert_group {alert_group_to_migrate['id']} for organization {organization_id}") - integration = AlertReceiveChannel.objects.get(pk=integration_data["id"]) - resolve_by_user_id = None - acknowledged_by_user_id = None - - if alert_group_to_migrate["resolved_by_user"]: - resolve_by_user_id = users.get(alert_group_to_migrate["resolved_by_user"], {}).get("id") - if alert_group_to_migrate["acknowledged_by_user"]: - acknowledged_by_user_id = users.get(alert_group_to_migrate["acknowledged_by_user"], {}).get("id") - - new_group = AlertGroup.all_objects.create( - channel=integration, - channel_filter_id=route_data["id"], - resolved=True, - resolved_by=alert_group_to_migrate["resolved_by"], - resolved_by_user_id=resolve_by_user_id, - resolved_at=alert_group_to_migrate.get("resolved_at") or timezone.now(), - acknowledged=alert_group_to_migrate["acknowledged"], - acknowledged_by=alert_group_to_migrate["acknowledged_by"], - acknowledged_by_user_id=acknowledged_by_user_id, - acknowledged_at=alert_group_to_migrate.get("acknowledged_at"), - ) - - new_group.started_at = convert_string_to_datetime(alert_group_to_migrate["created_at"]) - new_group.save(update_fields=["started_at"]) - - alert_group_data = { - "id": new_group.pk, - "old_public_primary_key": alert_group_to_migrate["id"], - } - - start_migration_alerts_task_id = AmixrMigrationTaskStatus.objects.get_migration_task_id( - organization_id=organization_id, name=start_migration_alerts.name - ) - start_migration_alerts.apply_async( - (api_token, organization_id, alert_group_data), - task_id=start_migration_alerts_task_id, - ) - - start_migration_logs_task_id = AmixrMigrationTaskStatus.objects.get_migration_task_id( - organization_id=organization_id, name=start_migration_logs.name - ) - start_migration_logs.apply_async( - (api_token, organization_id, users, alert_group_data), - task_id=start_migration_logs_task_id, - countdown=5, - ) - - current_task_id = migrate_alert_group.request.id - AmixrMigrationTaskStatus.objects.get(task_id=current_task_id).update_status_to_finished() - logger.info(f"Finished migration alert_group {alert_group_to_migrate['id']} for organization {organization_id}") - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=0 if settings.DEBUG else None -) -def start_migration_alerts(api_token, organization_id, alert_group_data): - logger.info( - f"Start migration alerts for alert_group {alert_group_data['old_public_primary_key']} " - f"for organization {organization_id}" - ) - AlertGroup = apps.get_model("alerts", "AlertGroup") - alert_group = AlertGroup.all_objects.get(pk=alert_group_data["id"]) - if not alert_group.alerts.exists(): - - url = "alerts?incident_id={}".format(alert_group_data["old_public_primary_key"]) - alerts = get_data_with_respect_to_pagination(api_token, url) - - for alert in alerts: - migrate_alerts_task_id = AmixrMigrationTaskStatus.objects.get_migration_task_id( - organization_id=organization_id, name=migrate_alert.name - ) - migrate_alert.apply_async( - (organization_id, alert_group_data, alert), - task_id=migrate_alerts_task_id, - ) - - current_task_id = start_migration_alerts.request.id - AmixrMigrationTaskStatus.objects.get(task_id=current_task_id).update_status_to_finished() - logger.info( - f"Finished 'start_migration_alerts' for alert_group {alert_group_data['old_public_primary_key']} " - f"for organization {organization_id}" - ) - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=0 if settings.DEBUG else None -) -def migrate_alert(organization_id, alert_group_data, alert): - logger.info(f"Start migration alert {alert['id']} for organization {organization_id}") - with transaction.atomic(): - new_alert = Alert( - title=alert["title"], - message=alert["message"], - image_url=alert["image_url"], - link_to_upstream_details=alert["link_to_upstream_details"], - group_id=alert_group_data["id"], - integration_unique_data=alert["payload"], - raw_request_data=alert["payload"], - ) - new_alert.save() - LockedAlert.objects.create(alert=new_alert) - new_alert.created_at = convert_string_to_datetime(alert["created_at"]) - new_alert.save(update_fields=["created_at"]) - - current_task_id = migrate_alert.request.id - AmixrMigrationTaskStatus.objects.get(task_id=current_task_id).update_status_to_finished() - logger.info(f"Finished migration alert {alert['id']} for organization {organization_id}") - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=0 if settings.DEBUG else None -) -def start_migration_logs(api_token, organization_id, users, alert_group_data): - logger.info(f"Start migration logs for alert_group {alert_group_data['id']} for organization {organization_id}") - url = "incident_logs?incident_id={}".format(alert_group_data["old_public_primary_key"]) - alert_group_logs = get_data_with_respect_to_pagination(api_token, url) - - for log in alert_group_logs: - migrate_logs_task_id = AmixrMigrationTaskStatus.objects.get_migration_task_id( - organization_id=organization_id, name=migrate_log.name - ) - migrate_log.apply_async( - (organization_id, users, alert_group_data, log), - task_id=migrate_logs_task_id, - ) - - current_task_id = start_migration_logs.request.id - AmixrMigrationTaskStatus.objects.get(task_id=current_task_id).update_status_to_finished() - logger.info( - f"Finished 'start_migration_logs' for alert_group {alert_group_data['id']} for organization {organization_id}" - ) - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=0 if settings.DEBUG else None -) -def migrate_log(organization_id, users, alert_group_data, log): - logger.info(f"Start migration log for alert_group {alert_group_data['id']} for organization {organization_id}") - log_author_id = users.get(log["author"], {}).get("id") - new_resolution_note = ResolutionNote( - author_id=log_author_id, - message_text=log["text"], - alert_group_id=alert_group_data["id"], - ) - new_resolution_note.save() - new_resolution_note.created_at = convert_string_to_datetime(log["created_at"]) - new_resolution_note.save(update_fields=["created_at"]) - - current_task_id = migrate_log.request.id - AmixrMigrationTaskStatus.objects.get(task_id=current_task_id).update_status_to_finished() - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=0 if settings.DEBUG else None -) -def start_migration_user_data(api_token, organization_id, users): - logger.info(f"Start migration user data for organization {organization_id}") - for user in users: - user_data = users[user] - migrate_user_data_task_id = AmixrMigrationTaskStatus.objects.get_migration_task_id( - organization_id=organization_id, name=migrate_user_data.name - ) - migrate_user_data.apply_async( - (api_token, organization_id, user_data), - task_id=migrate_user_data_task_id, - ) - - current_task_id = start_migration_user_data.request.id - AmixrMigrationTaskStatus.objects.get(task_id=current_task_id).update_status_to_finished() - logger.info(f"Finished 'start_migration_user_data' task for organization {organization_id}") - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=0 if settings.DEBUG else None -) -def migrate_user_data(api_token, organization_id, user_to_migrate): - logger.info(f"Start migration user {user_to_migrate['id']} for organization {organization_id}") - User = apps.get_model("user_management", "User") - UserNotificationPolicy = apps.get_model("base", "UserNotificationPolicy") - user = User.objects.filter(pk=user_to_migrate["id"], organization_id=organization_id).first() - - if user: - if not user.verified_phone_number and user_to_migrate["old_verified_phone_number"]: - user.save_verified_phone_number(user_to_migrate["old_verified_phone_number"]) - - url = "personal_notification_rules?user_id={}".format(user_to_migrate["old_public_primary_key"]) - user_notification_policies = get_data_with_respect_to_pagination(api_token, url) - - notification_policies_to_create = [] - existing_notification_policies_ids = list(user.notification_policies.all().values_list("pk", flat=True)) - - for notification_policy in user_notification_policies: - - try: - step, notification_channel = PersonalNotificationRuleSerializer._type_to_step_and_notification_channel( - notification_policy["type"], - ) - except exceptions.ValidationError: - continue - - new_notification_policy = UserNotificationPolicy( - user=user, - important=notification_policy["important"], - step=step, - order=notification_policy["position"], - ) - if step == UserNotificationPolicy.Step.NOTIFY: - new_notification_policy.notify_by = notification_channel - - if step == UserNotificationPolicy.Step.WAIT: - duration = notification_policy.get("duration") - wait_delay = timezone.timedelta(seconds=duration) if duration else UserNotificationPolicy.FIVE_MINUTES - new_notification_policy.wait_delay = wait_delay - - notification_policies_to_create.append(new_notification_policy) - - UserNotificationPolicy.objects.bulk_create(notification_policies_to_create, batch_size=5000) - user.notification_policies.filter(pk__in=existing_notification_policies_ids).delete() - - current_task_id = migrate_user_data.request.id - AmixrMigrationTaskStatus.objects.get(task_id=current_task_id).update_status_to_finished() - logger.info(f"Finished migration user {user_to_migrate['id']} for organization {organization_id}") diff --git a/engine/apps/migration_tool/urls.py b/engine/apps/migration_tool/urls.py deleted file mode 100644 index 8aa874d1..00000000 --- a/engine/apps/migration_tool/urls.py +++ /dev/null @@ -1,12 +0,0 @@ -from common.api_helpers.optional_slash_router import optional_slash_path - -from .views.customers_migration_tool import MigrateAPIView, MigrationPlanAPIView, MigrationStatusAPIView - -app_name = "migration-tool" - - -urlpatterns = [ - optional_slash_path("amixr_migration_plan", MigrationPlanAPIView.as_view(), name="amixr_migration_plan"), - optional_slash_path("migrate_from_amixr", MigrateAPIView.as_view(), name="migrate_from_amixr"), - optional_slash_path("amixr_migration_status", MigrationStatusAPIView.as_view(), name="amixr_migration_status"), -] diff --git a/engine/apps/migration_tool/utils.py b/engine/apps/migration_tool/utils.py deleted file mode 100644 index 0faa4efe..00000000 --- a/engine/apps/migration_tool/utils.py +++ /dev/null @@ -1,35 +0,0 @@ -import requests -from django.utils import timezone - -from apps.migration_tool.constants import REQUEST_URL - - -class APIResponseException(Exception): - pass - - -def get_data_with_respect_to_pagination(api_token, endpoint): - def fetch(url): - response = requests.get(url, headers={"AUTHORIZATION": api_token}) - if response.status_code != 200: - raise APIResponseException(f"Status code: {response.status_code}, Data: {response.content}") - return response.json() - - data = fetch(f"{REQUEST_URL}/{endpoint}") - results = data["results"] - - while data["next"]: - data = fetch(data["next"]) - - new_results = data["results"] - results.extend(new_results) - - return results - - -def convert_string_to_datetime(dt_str): - try: - dt = timezone.datetime.strptime(dt_str, "%Y-%m-%dT%X.%f%z") - except ValueError: - dt = timezone.datetime.strptime(dt_str, "%Y-%m-%dT%XZ") - return dt diff --git a/engine/apps/migration_tool/views/__init__.py b/engine/apps/migration_tool/views/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/engine/apps/migration_tool/views/customers_migration_tool.py b/engine/apps/migration_tool/views/customers_migration_tool.py deleted file mode 100644 index d34e18c0..00000000 --- a/engine/apps/migration_tool/views/customers_migration_tool.py +++ /dev/null @@ -1,186 +0,0 @@ -import logging - -import requests -from rest_framework import status -from rest_framework.permissions import IsAuthenticated -from rest_framework.response import Response -from rest_framework.views import APIView - -from apps.alerts.models import AlertReceiveChannel -from apps.api.permissions import IsAdmin, MethodPermission -from apps.auth_token.auth import PluginAuthentication -from apps.migration_tool.constants import FINISHED, IN_PROGRESS, NOT_STARTED, REQUEST_URL -from apps.migration_tool.tasks import start_migration_from_old_amixr -from apps.migration_tool.utils import get_data_with_respect_to_pagination -from common.api_helpers.exceptions import BadRequest - -logger = logging.getLogger(__name__) - - -class MigrationPlanAPIView(APIView): - authentication_classes = (PluginAuthentication,) - permission_classes = (IsAuthenticated, MethodPermission) - - method_permissions = {IsAdmin: ("POST",)} - - def post(self, request): - api_token = request.data.get("token", None) - if api_token is None: - raise BadRequest(detail="API token is required") - - organization = request.auth.organization - if organization.is_amixr_migration_started: - raise BadRequest(detail="Migration from Amixr has already been started") - - # check token - response = requests.get(f"{REQUEST_URL}/users", headers={"AUTHORIZATION": api_token}) - if response.status_code == status.HTTP_403_FORBIDDEN: - raise BadRequest(detail="Invalid token") - - # Just not to re-make the frontend... - USERS_NOT_TO_MIGRATE_KEY = ( - "Users WON'T be migrated (couldn't find those users in the Grafana Cloud, ask " - "them to sign up if you want their data to be migrated and re-build the migration plan)" - ) - - USERS_TO_MIGRATE = "Users will be migrated" - INTEGRATIONS_TO_MIGRATE = "Integrations to migrate" - INTEGRATIONS_COUNT = "Integrations count" - ROUTES_COUNT = "Routes count" - ESCALATIONS_POLICIES_COUNT = "Escalation policies count" - CALENDARS_COUNT = "Calendars count" - - migration_plan = { - USERS_TO_MIGRATE: [], - USERS_NOT_TO_MIGRATE_KEY: [], - INTEGRATIONS_TO_MIGRATE: [], - INTEGRATIONS_COUNT: 0, - ROUTES_COUNT: 0, - ESCALATIONS_POLICIES_COUNT: 0, - CALENDARS_COUNT: 0, - } - logger.info(f"migration plan for organization {organization.pk}: get users") - users = get_data_with_respect_to_pagination(api_token, "users") - logger.info(f"migration plan for organization {organization.pk}: got users") - org_users = organization.users.values_list("email", flat=True) - for user in users: - if user["email"] in org_users: - migration_plan[USERS_TO_MIGRATE].append(user["email"]) - else: - migration_plan[USERS_NOT_TO_MIGRATE_KEY].append(user["email"]) - - logger.info(f"migration plan for organization {organization.pk}: get integrations") - integrations = get_data_with_respect_to_pagination(api_token, "integrations") - logger.info(f"migration plan for organization {organization.pk}: got integrations") - existing_integrations_names = set(organization.alert_receive_channels.values_list("verbal_name", flat=True)) - - integrations_to_migrate_public_pk = [] - - for integration in integrations: - if integration["name"] in existing_integrations_names: - continue - - try: - integration_type = [ - key - for key, value in AlertReceiveChannel.INTEGRATIONS_TO_REVERSE_URL_MAP.items() - if value == integration["type"] - ][0] - except IndexError: - continue - if integration_type not in AlertReceiveChannel.WEB_INTEGRATION_CHOICES: - continue - - migration_plan[INTEGRATIONS_TO_MIGRATE].append(integration["name"]) - integrations_to_migrate_public_pk.append(integration["id"]) - - migration_plan[INTEGRATIONS_COUNT] = len(migration_plan[INTEGRATIONS_TO_MIGRATE]) - - routes_to_migrate_public_pk = [] - logger.info(f"migration plan for organization {organization.pk}: get routes") - routes = get_data_with_respect_to_pagination(api_token, "routes") - logger.info(f"migration plan for organization {organization.pk}: got routes") - - for route in routes: - if route["integration_id"] in integrations_to_migrate_public_pk: - migration_plan[ROUTES_COUNT] += 1 - routes_to_migrate_public_pk.append(route["id"]) - - logger.info(f"migration plan for organization {organization.pk}: get escalation_policies") - escalation_policies = get_data_with_respect_to_pagination(api_token, "escalation_policies") - logger.info(f"migration plan for organization {organization.pk}: got escalation_policies") - - for escalation_policy in escalation_policies: - if escalation_policy["route_id"] in routes_to_migrate_public_pk: - migration_plan[ESCALATIONS_POLICIES_COUNT] += 1 - - logger.info(f"migration plan for organization {organization.pk}: get schedules") - schedules = get_data_with_respect_to_pagination(api_token, "schedules") - logger.info(f"migration plan for organization {organization.pk}: got schedules") - - existing_schedules_names = set(organization.oncall_schedules.values_list("name", flat=True)) - for schedule in schedules: - if not schedule["ical_url"] or schedule["name"] in existing_schedules_names: - continue - migration_plan[CALENDARS_COUNT] += 1 - - return Response(migration_plan) - - -class MigrateAPIView(APIView): - authentication_classes = (PluginAuthentication,) - permission_classes = (IsAuthenticated, IsAdmin) - - def post(self, request): - api_token = request.data.get("token", None) - - if api_token is None: - raise BadRequest(detail="API token is required") - - organization = request.auth.organization - if organization.is_amixr_migration_started: - raise BadRequest(detail="Migration from Amixr has already been started") - # check token - response = requests.get(f"{REQUEST_URL}/users", headers={"AUTHORIZATION": api_token}) - if response.status_code == status.HTTP_403_FORBIDDEN: - raise BadRequest(detail="Invalid token") - - organization.is_amixr_migration_started = True - organization.save(update_fields=["is_amixr_migration_started"]) - - organization_id = organization.pk - user_id = request.user.pk - # start migration process - start_migration_from_old_amixr.delay(api_token=api_token, organization_id=organization_id, user_id=user_id) - return Response(status=status.HTTP_200_OK) - - -class MigrationStatusAPIView(APIView): - authentication_classes = (PluginAuthentication,) - permission_classes = (IsAuthenticated, IsAdmin) - - def get(self, request): - organization = request.auth.organization - migration_status = self.get_migration_status(organization) - endpoints_list = self.get_endpoints_list(organization) - return Response( - {"migration_status": migration_status, "endpoints_list": endpoints_list}, status=status.HTTP_200_OK - ) - - def get_migration_status(self, organization): - migration_status = NOT_STARTED - if organization.is_amixr_migration_started: - unfinished_tasks_exist = organization.migration_tasks.filter(is_finished=False).exists() - if unfinished_tasks_exist: - migration_status = IN_PROGRESS - else: - migration_status = FINISHED - return migration_status - - def get_endpoints_list(self, organization): - integrations = organization.alert_receive_channels.filter(team_id__isnull=True) - endpoints_list = [] - for integration in integrations: - integration_endpoint = f"{integration.verbal_name}, new endpoint: {integration.integration_url}" - endpoints_list.append(integration_endpoint) - return endpoints_list diff --git a/engine/engine/urls.py b/engine/engine/urls.py index e8080a7f..aeedbd4e 100644 --- a/engine/engine/urls.py +++ b/engine/engine/urls.py @@ -37,7 +37,6 @@ urlpatterns = [ path("integrations/v1/", include("apps.integrations.urls", namespace="integrations")), path("twilioapp/", include("apps.twilioapp.urls")), path("api/v1/", include("apps.public_api.urls", namespace="api-public")), - path("api/internal/v1/", include("apps.migration_tool.urls", namespace="migration-tool")), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) if settings.FEATURE_SLACK_INTEGRATION_ENABLED: diff --git a/engine/settings/base.py b/engine/settings/base.py index 92e759ed..6235c3e3 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -201,7 +201,6 @@ INSTALLED_APPS = [ "apps.public_api", "apps.grafana_plugin", "apps.grafana_plugin_management", - "apps.migration_tool", "corsheaders", "debug_toolbar", "social_django", diff --git a/grafana-plugin/src/pages/index.ts b/grafana-plugin/src/pages/index.ts index 15561d51..5c2478f4 100644 --- a/grafana-plugin/src/pages/index.ts +++ b/grafana-plugin/src/pages/index.ts @@ -10,7 +10,6 @@ import IncidentsPage2 from 'pages/incidents/Incidents'; import IntegrationsPage2 from 'pages/integrations/Integrations'; import LiveSettingsPage from 'pages/livesettings/LiveSettingsPage'; import MaintenancePage2 from 'pages/maintenance/Maintenance'; -import MigrationTool from 'pages/migration-tool/MigrationTool'; import OrganizationLogPage2 from 'pages/organization-logs/OrganizationLog'; import OutgoingWebhooks2 from 'pages/outgoing_webhooks/OutgoingWebhooks'; import SchedulePage from 'pages/schedule/Schedule'; @@ -125,13 +124,6 @@ export const pages: PageDefinition[] = [ text: 'Org Logs', hideFromTabs: true, }, - { - component: MigrationTool, - icon: 'import', - id: 'migration-tool', - text: 'Migrate From Amixr.IO', - hideFromTabs: true, - }, { component: CloudPage, icon: 'cloud', diff --git a/grafana-plugin/src/pages/migration-tool/MigrationTool.module.css b/grafana-plugin/src/pages/migration-tool/MigrationTool.module.css deleted file mode 100644 index 9005a623..00000000 --- a/grafana-plugin/src/pages/migration-tool/MigrationTool.module.css +++ /dev/null @@ -1,9 +0,0 @@ -.root { - margin-top: 24px; - width: 1000px; -} - -.root ul, -.root ol { - padding-left: 20px; -} diff --git a/grafana-plugin/src/pages/migration-tool/MigrationTool.tsx b/grafana-plugin/src/pages/migration-tool/MigrationTool.tsx deleted file mode 100644 index 4ed5a038..00000000 --- a/grafana-plugin/src/pages/migration-tool/MigrationTool.tsx +++ /dev/null @@ -1,364 +0,0 @@ -import React from 'react'; - -import { Button, LoadingPlaceholder, Tag, TextArea } from '@grafana/ui'; -import cn from 'classnames/bind'; -import { observer } from 'mobx-react'; -import Emoji from 'react-emoji-render'; - -import Text from 'components/Text/Text'; -import { makeRequest } from 'network'; -import { withMobXProviderContext } from 'state/withStore'; -import { showApiError } from 'utils'; - -import apiTokensImg from './img/api-tokens.png'; - -import styles from './MigrationTool.module.css'; - -const cx = cn.bind(styles); - -interface MigrationToolProps {} - -interface MigrationToolState { - migrationStatus?: MIGRATION_STATUS; - migrationInitiated?: boolean; - migrationPlan?: any; - apiKey: string; - endpointsList?: string[]; -} - -enum MIGRATION_STATUS { - NOT_STARTED = 'not_started', - IN_PROGRESS = 'in_progress', - FINISHED = 'finished', -} - -function scrollIntoView(node: HTMLDivElement | null) { - if (node) { - node.scrollIntoView({ behavior: 'smooth' }); - } -} - -@observer -class MigrationToolPage extends React.Component { - state: MigrationToolState = { - migrationStatus: undefined, - migrationInitiated: false, - migrationPlan: undefined, - apiKey: '', - }; - - statusTimer: ReturnType; - - async componentDidMount() { - this.checkStatus(); - } - - render() { - const { migrationStatus, migrationInitiated, migrationPlan, apiKey, endpointsList } = this.state; - - return ( -
-

- - Migrate From Amixr.IO - -

-
-

Dear Amixr.IO customers!

-

- Amixr Inc. was acquired by Grafana Labs in 2021. Grafana OnCall is a very similar product, and most of you - shouldn’t notice a lot of difference. We used the chance and made a few core improvements, which we hope - will make your experience much better. -

-

- We understand that it will put additional work on your shoulders. We did our best to prepare a migration - tool to assist you with the migration process. -

-

- Unfortunately, we no longer plan to support Amixr.IO and expect to shut down Amixr.IO to be able to focus on - Grafana OnCall. The timeline is: -

    -
  • 1 June 2022 - 31 June 2022 the migration tool will be available.
  • -
  • 30st of June Amixr.IO and all services located in this domain will be disabled.
  • -
-

-

- How to prepare for the migration -

    -
  1. - Ask all users from your Amixr.IO workspace to{' '} - - sign up - {' '} - in the Grafana Cloud. -
  2. -
  3. Request the migration plan.
  4. -
-

-

- For any technical assistance please reach out to our team in{' '} - - Grafana Slack channel #grafana-oncall - - . We’ll be happy to give you a hand and help you with migration on a call. -

-

For any questions related to pricing, or payments, please reach out to our sales:

-

- For any other questions: -

-

-
- {migrationStatus ? ( - <> - {migrationStatus === MIGRATION_STATUS.NOT_STARTED && ( - <> -
-
-

- Initiate migration to Grafana OnCall -

-

Find API key in your Amixr.IO workspace -> bottom of the “Settings” page:

-

- -

-

Add Amixr.IO API Key to the field below:

-

-