diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb574046..29f34083 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,9 @@ jobs: cd grafana-plugin/ yarn --network-timeout 500000 yarn build + # pre-commit uses git, which is not working in the action without this workaround + # see https://github.com/actions/runner-images/issues/6775 + - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Lint All run: | pre-commit run --all-files @@ -55,6 +58,32 @@ jobs: run: | docker run -v ${PWD}/docs/sources:/hugo/content/docs/oncall/latest -e HUGO_REFLINKSERRORLEVEL=ERROR --rm grafana/docs-base:latest /bin/bash -c 'make hugo' + lint-migrations-backend-mysql-rabbitmq: + name: "Lint migrations" + runs-on: ubuntu-latest + container: python:3.9 + env: + DJANGO_SETTINGS_MODULE: settings.ci-test + SLACK_CLIENT_OAUTH_ID: 1 + services: + rabbit_test: + image: rabbitmq:3.7.19 + env: + RABBITMQ_DEFAULT_USER: rabbitmq + RABBITMQ_DEFAULT_PASS: rabbitmq + mysql_test: + image: mysql:5.7.25 + env: + MYSQL_DATABASE: oncall_local_dev + MYSQL_ROOT_PASSWORD: local_dev_pwd + steps: + - uses: actions/checkout@v3 + - name: Lint migrations + run: | + cd engine/ + pip install -r requirements.txt + python manage.py lintmigrations + unit-test-backend-mysql-rabbitmq: name: "Backend Tests: MySQL + RabbitMQ (RBAC enabled: ${{ matrix.rbac_enabled }})" runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index cce7d1ee..0c5940d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## v1.1.23 (2023-02-06) + +### Fixed + +- Fix bug with email case sensitivity for ICal on-call schedules ([1297](https://github.com/grafana/oncall/pull/1297)) + ## v1.1.22 (2023-02-03) ### Fixed diff --git a/dev/README.md b/dev/README.md index bd88dabf..15b3fd9c 100644 --- a/dev/README.md +++ b/dev/README.md @@ -20,6 +20,7 @@ - [symbol not found in flat namespace '\_EVP_DigestSignUpdate'](#symbol-not-found-in-flat-namespace-_evp_digestsignupdate) - [IDE Specific Instructions](#ide-specific-instructions) - [PyCharm](#pycharm) +- [How to write database migrations](#how-to-write-database-migrations) Related: [How to develop integrations](/engine/config_integrations/README.md) @@ -397,3 +398,15 @@ make run-backend-celery 5. Create a new Django Server run configuration to Run/Debug the engine - Use a plugin such as EnvFile to load the .env.dev file - Change port from 8000 to 8080 + +## How to write database migrations + +We use [django-migration-linter](https://github.com/3YOURMIND/django-migration-linter) to keep database migrations +backwards compatible + +- we can automatically run migrations and they are zero-downtime, e.g. old code can work with the migrated database +- we can run and rollback migrations without worrying about data safety +- OnCall is deployed to the multiple environments core team is not able to control + +See [django-migration-linter checklist](https://github.com/3YOURMIND/django-migration-linter/blob/main/docs/incompatibilities.md) +for the common mistakes and best practices diff --git a/docs/sources/integrations/chatops-integrations/configure-slack/index.md b/docs/sources/integrations/chatops-integrations/configure-slack/index.md index e9595ba6..7e00d3db 100644 --- a/docs/sources/integrations/chatops-integrations/configure-slack/index.md +++ b/docs/sources/integrations/chatops-integrations/configure-slack/index.md @@ -88,12 +88,19 @@ The Grafana OnCall Slack app includes helpful message shortcuts and slash comman ### Slack commands -Use the `/oncall` Slack command to create a new alert group directly from Slack. +Use the `/oncall` Slack command to create a new alert group directly from Slack targetting a team and/or route. 1. Type `/oncall` in the message box of the desired Slack channel then click **Send**. 1. Fill out the **Start New Escalation** creation form then click **Submit**. 1. Once the Grafana OnCall app sends a Slack message with the newly created alert, the alert group is open and firing. +Use the `/escalate` Slack command to create a new alert group directly from Slack and specifically paging a user or +a schedule. + +1. Type `/escalate` in the message box of any Slack channel then click **Send**. +1. Fill out the **Create alert group** form then click **Submit**. +1. Once the Grafana OnCall app sends a Slack message with the newly created alert, the alert group is open and firing. + ### Message shortcuts Use message shortcuts to add resolution notes directly from Slack. Message shortcuts are available in the More actions menu from any message. diff --git a/docs/sources/open-source/_index.md b/docs/sources/open-source/_index.md index aad2c29c..f5f541ea 100644 --- a/docs/sources/open-source/_index.md +++ b/docs/sources/open-source/_index.md @@ -96,7 +96,11 @@ features: slash_commands: - command: /oncall url: /slack/interactive_api_endpoint/ - description: oncall + description: Create a manual alert group + should_escape: false + - command: /escalate + url: /slack/interactive_api_endpoint/ + description: Direct page user(s) or schedule(s) should_escape: false oauth_config: redirect_urls: diff --git a/engine/apps/alerts/migrations/0001_squashed_initial.py b/engine/apps/alerts/migrations/0001_squashed_initial.py index bc66bc5c..0c96d7d4 100644 --- a/engine/apps/alerts/migrations/0001_squashed_initial.py +++ b/engine/apps/alerts/migrations/0001_squashed_initial.py @@ -15,6 +15,7 @@ import django.core.validators from django.db import migrations, models import django.db.models.deletion import django.db.models.manager +import django_migration_linter as linter from apps.alerts.integration_options_mixin import IntegrationOptionsMixin @@ -27,6 +28,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='Alert', fields=[ diff --git a/engine/apps/alerts/migrations/0002_squashed_initial.py b/engine/apps/alerts/migrations/0002_squashed_initial.py index bd0b33dc..765e8bfa 100644 --- a/engine/apps/alerts/migrations/0002_squashed_initial.py +++ b/engine/apps/alerts/migrations/0002_squashed_initial.py @@ -3,6 +3,7 @@ from django.db import migrations, models import django.db.models.deletion import django.db.models.manager +import django_migration_linter as linter class Migration(migrations.Migration): @@ -18,6 +19,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='userhasnotification', name='user', diff --git a/engine/apps/alerts/migrations/0003_grafanaalertingcontactpoint_datasource_uid.py b/engine/apps/alerts/migrations/0003_grafanaalertingcontactpoint_datasource_uid.py index 4bdcec63..2962923a 100644 --- a/engine/apps/alerts/migrations/0003_grafanaalertingcontactpoint_datasource_uid.py +++ b/engine/apps/alerts/migrations/0003_grafanaalertingcontactpoint_datasource_uid.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.13 on 2022-06-14 15:18 from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='grafanaalertingcontactpoint', name='datasource_uid', diff --git a/engine/apps/alerts/migrations/0004_auto_20220711_1106.py b/engine/apps/alerts/migrations/0004_auto_20220711_1106.py index ddae2447..6e0396b8 100644 --- a/engine/apps/alerts/migrations/0004_auto_20220711_1106.py +++ b/engine/apps/alerts/migrations/0004_auto_20220711_1106.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.13 on 2022-07-11 11:06 from django.db import migrations +import django_migration_linter as linter class Migration(migrations.Migration): @@ -17,6 +18,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), # migrations.RemoveField( # model_name='alertgroup', # name='active_cache_for_web_calculation_id', diff --git a/engine/apps/alerts/migrations/0005_alertgroup_cached_render_for_web.py b/engine/apps/alerts/migrations/0005_alertgroup_cached_render_for_web.py index cff73da3..7717a56a 100644 --- a/engine/apps/alerts/migrations/0005_alertgroup_cached_render_for_web.py +++ b/engine/apps/alerts/migrations/0005_alertgroup_cached_render_for_web.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.13 on 2022-07-20 09:04 from django.db import migrations, models, OperationalError, ProgrammingError +import django_migration_linter as linter class AddFieldIfNotExists(migrations.AddField): @@ -36,6 +37,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), AddFieldIfNotExists( model_name='alertgroup', name='cached_render_for_web', diff --git a/engine/apps/alerts/migrations/0006_alertgroup_alerts_aler_channel_ee84a7_idx.py b/engine/apps/alerts/migrations/0006_alertgroup_alerts_aler_channel_ee84a7_idx.py index ada7b0da..d3ec42ab 100644 --- a/engine/apps/alerts/migrations/0006_alertgroup_alerts_aler_channel_ee84a7_idx.py +++ b/engine/apps/alerts/migrations/0006_alertgroup_alerts_aler_channel_ee84a7_idx.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.13 on 2022-07-27 10:51 from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddIndex( model_name='alertgroup', index=models.Index(fields=['channel_id', 'resolved', 'acknowledged', 'silenced', 'root_alert_group_id', 'is_archived'], name='alerts_aler_channel_ee84a7_idx'), diff --git a/engine/apps/alerts/migrations/0007_populate_web_title_cache.py b/engine/apps/alerts/migrations/0007_populate_web_title_cache.py index 9869a9da..09ada136 100644 --- a/engine/apps/alerts/migrations/0007_populate_web_title_cache.py +++ b/engine/apps/alerts/migrations/0007_populate_web_title_cache.py @@ -4,6 +4,7 @@ from django.db import migrations from apps.alerts.models import AlertReceiveChannel from apps.alerts.tasks import update_web_title_cache_for_alert_receive_channel +import django_migration_linter as linter def populate_web_title_cache(apps, _): @@ -19,6 +20,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.RenameField( model_name='alertgroup', old_name='verbose_name', diff --git a/engine/apps/alerts/migrations/0008_alter_alertgrouplogrecord_type.py b/engine/apps/alerts/migrations/0008_alter_alertgrouplogrecord_type.py index f805f599..08ea965c 100644 --- a/engine/apps/alerts/migrations/0008_alter_alertgrouplogrecord_type.py +++ b/engine/apps/alerts/migrations/0008_alter_alertgrouplogrecord_type.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.16 on 2023-01-19 18:06 from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AlterField( model_name='alertgrouplogrecord', name='type', diff --git a/engine/apps/api_for_grafana_incident/serializers.py b/engine/apps/api_for_grafana_incident/serializers.py index c6668933..5b6cf049 100644 --- a/engine/apps/api_for_grafana_incident/serializers.py +++ b/engine/apps/api_for_grafana_incident/serializers.py @@ -2,17 +2,30 @@ import logging from rest_framework import serializers -from apps.alerts.models import AlertGroup -from common.api_helpers.mixins import EagerLoadingMixin +from apps.alerts.models import Alert, AlertGroup logger = logging.getLogger(__name__) -class AlertGroupSerializer(EagerLoadingMixin, serializers.ModelSerializer): +class AlertSerializer(serializers.ModelSerializer): id_oncall = serializers.CharField(read_only=True, source="public_primary_key") + payload = serializers.JSONField(read_only=True, source="raw_request_data") + + class Meta: + model = Alert + fields = [ + "id_oncall", + "payload", + ] + + +class AlertGroupSerializer(serializers.ModelSerializer): + + id = serializers.CharField(read_only=True, source="public_primary_key") status = serializers.SerializerMethodField(source="get_status") link = serializers.CharField(read_only=True, source="web_link") + alerts = AlertSerializer(many=True, read_only=True) def get_status(self, obj): return next(filter(lambda status: status[0] == obj.status, AlertGroup.STATUS_CHOICES))[1].lower() @@ -20,7 +33,8 @@ class AlertGroupSerializer(EagerLoadingMixin, serializers.ModelSerializer): class Meta: model = AlertGroup fields = [ - "id_oncall", + "id", "link", "status", + "alerts", ] diff --git a/engine/apps/auth_token/migrations/0001_squashed_initial.py b/engine/apps/auth_token/migrations/0001_squashed_initial.py index 7c7fe23a..b816d815 100644 --- a/engine/apps/auth_token/migrations/0001_squashed_initial.py +++ b/engine/apps/auth_token/migrations/0001_squashed_initial.py @@ -3,6 +3,7 @@ from django.utils import timezone import apps.auth_token.models.slack_auth_token from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -13,6 +14,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='ApiAuthToken', fields=[ diff --git a/engine/apps/auth_token/migrations/0002_squashed_initial.py b/engine/apps/auth_token/migrations/0002_squashed_initial.py index e35e5ce2..8141df51 100644 --- a/engine/apps/auth_token/migrations/0002_squashed_initial.py +++ b/engine/apps/auth_token/migrations/0002_squashed_initial.py @@ -2,6 +2,7 @@ from django.db import migrations, models import django.db.models.deletion +import django_migration_linter as linter class Migration(migrations.Migration): @@ -15,6 +16,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='userscheduleexportauthtoken', name='organization', diff --git a/engine/apps/auth_token/migrations/0003_auto_20221121_1610.py b/engine/apps/auth_token/migrations/0003_auto_20221121_1610.py index 08406946..df003a55 100644 --- a/engine/apps/auth_token/migrations/0003_auto_20221121_1610.py +++ b/engine/apps/auth_token/migrations/0003_auto_20221121_1610.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.16 on 2022-11-21 16:10 from django.db import migrations +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.RemoveField( model_name='mobileappverificationtoken', name='organization', diff --git a/engine/apps/base/migrations/0001_squashed_initial.py b/engine/apps/base/migrations/0001_squashed_initial.py index 0055fa88..ee0572ef 100644 --- a/engine/apps/base/migrations/0001_squashed_initial.py +++ b/engine/apps/base/migrations/0001_squashed_initial.py @@ -6,6 +6,7 @@ import datetime import django.core.validators from django.db import migrations, models import django.db.models.deletion +import django_migration_linter as linter class Migration(migrations.Migration): @@ -17,6 +18,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='DynamicSetting', fields=[ diff --git a/engine/apps/base/migrations/0002_squashed_initial.py b/engine/apps/base/migrations/0002_squashed_initial.py index 9bf98adb..18cb6bcc 100644 --- a/engine/apps/base/migrations/0002_squashed_initial.py +++ b/engine/apps/base/migrations/0002_squashed_initial.py @@ -2,6 +2,7 @@ from django.db import migrations, models import django.db.models.deletion +import django_migration_linter as linter class Migration(migrations.Migration): @@ -14,6 +15,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='usernotificationpolicylogrecord', name='author', diff --git a/engine/apps/base/migrations/0003_delete_organizationlogrecord.py b/engine/apps/base/migrations/0003_delete_organizationlogrecord.py index b0a49a1a..c5515881 100644 --- a/engine/apps/base/migrations/0003_delete_organizationlogrecord.py +++ b/engine/apps/base/migrations/0003_delete_organizationlogrecord.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.5 on 2022-08-23 12:03 from django.db import migrations +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.DeleteModel( name='OrganizationLogRecord', ), diff --git a/engine/apps/email/migrations/0001_initial.py b/engine/apps/email/migrations/0001_initial.py index 3fe9537a..8b5a7e94 100644 --- a/engine/apps/email/migrations/0001_initial.py +++ b/engine/apps/email/migrations/0001_initial.py @@ -3,6 +3,7 @@ from django.db import migrations, models import django.db.models.deletion import uuid +import django_migration_linter as linter class Migration(migrations.Migration): @@ -16,6 +17,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='EmailMessage', fields=[ diff --git a/engine/apps/heartbeat/migrations/0001_squashed_initial.py b/engine/apps/heartbeat/migrations/0001_squashed_initial.py index a30b29e7..687c9a3c 100644 --- a/engine/apps/heartbeat/migrations/0001_squashed_initial.py +++ b/engine/apps/heartbeat/migrations/0001_squashed_initial.py @@ -4,6 +4,7 @@ import apps.heartbeat.models import django.core.validators from django.db import migrations, models import django.db.models.deletion +import django_migration_linter as linter class Migration(migrations.Migration): @@ -15,6 +16,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='IntegrationHeartBeat', fields=[ diff --git a/engine/apps/mobile_app/migrations/0001_initial.py b/engine/apps/mobile_app/migrations/0001_initial.py index de3faf32..a52c649c 100644 --- a/engine/apps/mobile_app/migrations/0001_initial.py +++ b/engine/apps/mobile_app/migrations/0001_initial.py @@ -3,6 +3,7 @@ import apps.mobile_app.models from django.db import migrations, models import django.db.models.deletion +import django_migration_linter as linter class Migration(migrations.Migration): @@ -15,6 +16,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='MobileAppVerificationToken', fields=[ diff --git a/engine/apps/oss_installation/migrations/0001_squashed_initial.py b/engine/apps/oss_installation/migrations/0001_squashed_initial.py index b1a34cbd..fc7def6e 100644 --- a/engine/apps/oss_installation/migrations/0001_squashed_initial.py +++ b/engine/apps/oss_installation/migrations/0001_squashed_initial.py @@ -2,6 +2,7 @@ from django.db import migrations, models import uuid +import django_migration_linter as linter class Migration(migrations.Migration): @@ -12,6 +13,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='CloudHeartbeat', fields=[ diff --git a/engine/apps/schedules/ical_utils.py b/engine/apps/schedules/ical_utils.py index fd6cd341..5680b652 100644 --- a/engine/apps/schedules/ical_utils.py +++ b/engine/apps/schedules/ical_utils.py @@ -72,7 +72,11 @@ def users_in_ical( if users_to_filter is not None: return list( - {user for user in users_to_filter if user.username in usernames_from_ical or user.email in emails_from_ical} + { + user + for user in users_to_filter + if user.username in usernames_from_ical or user.email.lower() in emails_from_ical + } ) users_found_in_ical = organization.users diff --git a/engine/apps/schedules/migrations/0001_squashed_initial.py b/engine/apps/schedules/migrations/0001_squashed_initial.py index 8456b7fe..e55e1aba 100644 --- a/engine/apps/schedules/migrations/0001_squashed_initial.py +++ b/engine/apps/schedules/migrations/0001_squashed_initial.py @@ -6,6 +6,7 @@ import django.core.validators from django.db import migrations, models import django.db.models.deletion import uuid +import django_migration_linter as linter class Migration(migrations.Migration): @@ -16,6 +17,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='CustomOnCallShift', fields=[ diff --git a/engine/apps/schedules/migrations/0002_squashed_initial.py b/engine/apps/schedules/migrations/0002_squashed_initial.py index d5e25ccd..c33cf204 100644 --- a/engine/apps/schedules/migrations/0002_squashed_initial.py +++ b/engine/apps/schedules/migrations/0002_squashed_initial.py @@ -2,6 +2,7 @@ from django.db import migrations, models import django.db.models.deletion +import django_migration_linter as linter class Migration(migrations.Migration): @@ -16,6 +17,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='oncallschedule', name='organization', diff --git a/engine/apps/schedules/migrations/0003_alter_customoncallshift_frequency.py b/engine/apps/schedules/migrations/0003_alter_customoncallshift_frequency.py index 239ce026..fa56dd7c 100644 --- a/engine/apps/schedules/migrations/0003_alter_customoncallshift_frequency.py +++ b/engine/apps/schedules/migrations/0003_alter_customoncallshift_frequency.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.5 on 2022-06-28 13:06 from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AlterField( model_name='customoncallshift', name='frequency', diff --git a/engine/apps/schedules/migrations/0004_customoncallshift_until.py b/engine/apps/schedules/migrations/0004_customoncallshift_until.py index 46c3c8e2..3f501cfa 100644 --- a/engine/apps/schedules/migrations/0004_customoncallshift_until.py +++ b/engine/apps/schedules/migrations/0004_customoncallshift_until.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.5 on 2022-06-28 13:07 from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='customoncallshift', name='until', diff --git a/engine/apps/schedules/migrations/0005_auto_20220704_1947.py b/engine/apps/schedules/migrations/0005_auto_20220704_1947.py index 03b77e60..aa159184 100644 --- a/engine/apps/schedules/migrations/0005_auto_20220704_1947.py +++ b/engine/apps/schedules/migrations/0005_auto_20220704_1947.py @@ -2,6 +2,7 @@ from django.db import migrations, models import django.db.models.deletion +import django_migration_linter as linter class Migration(migrations.Migration): @@ -11,6 +12,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='OnCallScheduleWeb', fields=[ diff --git a/engine/apps/schedules/migrations/0006_customoncallshift_rotation_start.py b/engine/apps/schedules/migrations/0006_customoncallshift_rotation_start.py index 682df514..3a1c02e0 100644 --- a/engine/apps/schedules/migrations/0006_customoncallshift_rotation_start.py +++ b/engine/apps/schedules/migrations/0006_customoncallshift_rotation_start.py @@ -2,6 +2,7 @@ from django.db import migrations, models from django.db.models import F +import django_migration_linter as linter def fill_rotation_start_field(apps, schema_editor): @@ -16,6 +17,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='customoncallshift', name='rotation_start', diff --git a/engine/apps/schedules/migrations/0007_customoncallshift_updated_shift.py b/engine/apps/schedules/migrations/0007_customoncallshift_updated_shift.py index c034f53a..a7c36c0b 100644 --- a/engine/apps/schedules/migrations/0007_customoncallshift_updated_shift.py +++ b/engine/apps/schedules/migrations/0007_customoncallshift_updated_shift.py @@ -2,6 +2,7 @@ from django.db import migrations, models import django.db.models.deletion +import django_migration_linter as linter class Migration(migrations.Migration): @@ -11,6 +12,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='customoncallshift', name='updated_shift', diff --git a/engine/apps/schedules/migrations/0008_auto_20221201_0809.py b/engine/apps/schedules/migrations/0008_auto_20221201_0809.py index 0aee5c7e..27b9b6ad 100644 --- a/engine/apps/schedules/migrations/0008_auto_20221201_0809.py +++ b/engine/apps/schedules/migrations/0008_auto_20221201_0809.py @@ -5,6 +5,7 @@ from django.db import migrations from django.db.models import Q from common.timezones import is_valid_timezone +import django_migration_linter as linter def fix_bad_timezone_values(model): @@ -34,6 +35,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.RunPython(fix_bad_timezone_values('CustomOnCallShift')), migrations.RunPython(fix_bad_timezone_values('OnCallScheduleCalendar')), migrations.RunPython(fix_bad_timezone_values('OnCallScheduleWeb')), diff --git a/engine/apps/schedules/tests/calendars/override_email_case_sensitivity.ics b/engine/apps/schedules/tests/calendars/override_email_case_sensitivity.ics new file mode 100644 index 00000000..8b6a4430 --- /dev/null +++ b/engine/apps/schedules/tests/calendars/override_email_case_sensitivity.ics @@ -0,0 +1,21 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:test +X-WR-TIMEZONE:Europe/London +BEGIN:VEVENT +DTSTART:20230206T110000Z +DTEND:20230206T120000Z +DTSTAMP:20230206T112228Z +UID:16m6o1qji0fdg49coeflc202ss@google.com +CREATED:20230206T112217Z +DESCRIPTION: +LAST-MODIFIED:20230206T112217Z +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Test@TEST.test +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/engine/apps/schedules/tests/test_custom_on_call_shift.py b/engine/apps/schedules/tests/test_custom_on_call_shift.py index 7974ad91..47ff1d35 100644 --- a/engine/apps/schedules/tests/test_custom_on_call_shift.py +++ b/engine/apps/schedules/tests/test_custom_on_call_shift.py @@ -1382,6 +1382,37 @@ def test_get_oncall_users_for_multiple_schedules( ) +@pytest.mark.django_db +def test_get_oncall_users_for_multiple_schedules_emails_case_insensitive( + get_ical, + make_organization, + make_user_for_organization, + make_on_call_shift, + make_schedule, +): + """ + Test that emails are case insensitive when matching users to on-call shifts. + https://github.com/grafana/oncall/issues/1296 + """ + organization = make_organization() + + # user's email case is the opposite of the one in the ICal file below (Test@TEST.test) + user = make_user_for_organization(organization, email="tEST@test.TEST") + schedule = make_schedule(organization, schedule_class=OnCallScheduleCalendar) + + # Load ICal file with an event for user with email Test@TEST.test for 6 February 2023, 11:00 UTC - 12:00 UTC + calendar = get_ical("override_email_case_sensitivity.ics") + schedule.cached_ical_file_overrides = calendar.to_ical().decode() + schedule.save(update_fields=["cached_ical_file_overrides"]) + + # Get on-call users for 6 February 2023 11:30 UTC + events_datetime = timezone.datetime(2023, 2, 6, 11, 30, tzinfo=timezone.utc) + schedules = OnCallSchedule.objects.filter(pk=schedule.pk) + oncall_users = schedules.get_oncall_users(events_datetime=events_datetime) + + assert oncall_users == {schedule.pk: [user]} + + @pytest.mark.django_db def test_shift_convert_to_ical(make_organization_and_user, make_on_call_shift): organization, user = make_organization_and_user() diff --git a/engine/apps/slack/migrations/0001_squashed_initial.py b/engine/apps/slack/migrations/0001_squashed_initial.py index 7bc20cbb..3a3140d2 100644 --- a/engine/apps/slack/migrations/0001_squashed_initial.py +++ b/engine/apps/slack/migrations/0001_squashed_initial.py @@ -6,6 +6,7 @@ import django.core.validators from django.db import migrations, models import django.db.models.deletion import uuid +import django_migration_linter as linter class Migration(migrations.Migration): @@ -17,6 +18,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='SlackActionRecord', fields=[ diff --git a/engine/apps/slack/migrations/0002_squashed_initial.py b/engine/apps/slack/migrations/0002_squashed_initial.py index 3f322e6d..d0595e79 100644 --- a/engine/apps/slack/migrations/0002_squashed_initial.py +++ b/engine/apps/slack/migrations/0002_squashed_initial.py @@ -2,6 +2,7 @@ from django.db import migrations, models import django.db.models.deletion +import django_migration_linter as linter class Migration(migrations.Migration): @@ -14,6 +15,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='slackmessage', name='organization', diff --git a/engine/apps/telegram/migrations/0001_squashed_initial.py b/engine/apps/telegram/migrations/0001_squashed_initial.py index 196726e0..aea99590 100644 --- a/engine/apps/telegram/migrations/0001_squashed_initial.py +++ b/engine/apps/telegram/migrations/0001_squashed_initial.py @@ -5,6 +5,7 @@ import django.core.validators from django.db import migrations, models import django.db.models.deletion import uuid +import django_migration_linter as linter class Migration(migrations.Migration): @@ -17,6 +18,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='TelegramVerificationCode', fields=[ diff --git a/engine/apps/telegram/migrations/0002_alter_telegrammessage_message_type.py b/engine/apps/telegram/migrations/0002_alter_telegrammessage_message_type.py index 96415381..8e59f282 100644 --- a/engine/apps/telegram/migrations/0002_alter_telegrammessage_message_type.py +++ b/engine/apps/telegram/migrations/0002_alter_telegrammessage_message_type.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.16 on 2022-12-29 14:42 from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AlterField( model_name='telegrammessage', name='message_type', diff --git a/engine/apps/twilioapp/migrations/0001_squashed_initial.py b/engine/apps/twilioapp/migrations/0001_squashed_initial.py index cd76ebdf..ffd3183f 100644 --- a/engine/apps/twilioapp/migrations/0001_squashed_initial.py +++ b/engine/apps/twilioapp/migrations/0001_squashed_initial.py @@ -2,6 +2,7 @@ from django.db import migrations, models import django.db.models.deletion +import django_migration_linter as linter class Migration(migrations.Migration): @@ -15,6 +16,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='TwilioLogRecord', fields=[ diff --git a/engine/apps/twilioapp/migrations/0002_auto_20220604_1008.py b/engine/apps/twilioapp/migrations/0002_auto_20220604_1008.py index cddd898c..4771d7a1 100644 --- a/engine/apps/twilioapp/migrations/0002_auto_20220604_1008.py +++ b/engine/apps/twilioapp/migrations/0002_auto_20220604_1008.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.5 on 2022-06-04 10:08 from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='phonecall', name='grafana_cloud_notification', diff --git a/engine/apps/user_management/migrations/0001_squashed_initial.py b/engine/apps/user_management/migrations/0001_squashed_initial.py index e88a5acb..c2308b64 100644 --- a/engine/apps/user_management/migrations/0001_squashed_initial.py +++ b/engine/apps/user_management/migrations/0001_squashed_initial.py @@ -8,6 +8,7 @@ import django.core.validators from django.db import migrations, models import django.db.models.deletion import mirage.fields +import django_migration_linter as linter class Migration(migrations.Migration): @@ -20,6 +21,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='Organization', fields=[ diff --git a/engine/apps/user_management/migrations/0002_auto_20220705_1214.py b/engine/apps/user_management/migrations/0002_auto_20220705_1214.py index 976bfe7a..fce96fe1 100644 --- a/engine/apps/user_management/migrations/0002_auto_20220705_1214.py +++ b/engine/apps/user_management/migrations/0002_auto_20220705_1214.py @@ -2,6 +2,7 @@ import apps.user_management.models.user from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -11,6 +12,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='user', name='_timezone', diff --git a/engine/apps/user_management/migrations/0003_user_hide_phone_number.py b/engine/apps/user_management/migrations/0003_user_hide_phone_number.py index e1d3a0ed..272e94a0 100644 --- a/engine/apps/user_management/migrations/0003_user_hide_phone_number.py +++ b/engine/apps/user_management/migrations/0003_user_hide_phone_number.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.5 on 2022-08-25 08:51 from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='user', name='hide_phone_number', diff --git a/engine/apps/user_management/migrations/0004_auto_20221025_0316.py b/engine/apps/user_management/migrations/0004_auto_20221025_0316.py index 8d1b15c8..01707dfd 100644 --- a/engine/apps/user_management/migrations/0004_auto_20221025_0316.py +++ b/engine/apps/user_management/migrations/0004_auto_20221025_0316.py @@ -2,6 +2,7 @@ from django.db import migrations, models import django.db.models.deletion +import django_migration_linter as linter class Migration(migrations.Migration): @@ -11,6 +12,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.CreateModel( name='Region', fields=[ diff --git a/engine/apps/user_management/migrations/0005_rbac_permissions.py b/engine/apps/user_management/migrations/0005_rbac_permissions.py index 560aa144..e1f4d5d7 100644 --- a/engine/apps/user_management/migrations/0005_rbac_permissions.py +++ b/engine/apps/user_management/migrations/0005_rbac_permissions.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.15 on 2022-10-25 11:18 from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='organization', name='is_rbac_permissions_enabled', diff --git a/engine/apps/user_management/migrations/0006_organization_uuid.py b/engine/apps/user_management/migrations/0006_organization_uuid.py index ab2e1d2b..e58c0bd6 100644 --- a/engine/apps/user_management/migrations/0006_organization_uuid.py +++ b/engine/apps/user_management/migrations/0006_organization_uuid.py @@ -2,6 +2,7 @@ from django.db import migrations, models import uuid +import django_migration_linter as linter def fill_org_uuid(apps, schema_editor): @@ -20,6 +21,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='organization', name='uuid', diff --git a/engine/apps/user_management/migrations/0007_organization_deleted_at.py b/engine/apps/user_management/migrations/0007_organization_deleted_at.py index fd80179e..76a718c5 100644 --- a/engine/apps/user_management/migrations/0007_organization_deleted_at.py +++ b/engine/apps/user_management/migrations/0007_organization_deleted_at.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.16 on 2023-01-04 05:06 from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='organization', name='deleted_at', diff --git a/engine/apps/user_management/migrations/0008_organization_is_grafana_incident_enabled.py b/engine/apps/user_management/migrations/0008_organization_is_grafana_incident_enabled.py index 3c8598e9..b7d75913 100644 --- a/engine/apps/user_management/migrations/0008_organization_is_grafana_incident_enabled.py +++ b/engine/apps/user_management/migrations/0008_organization_is_grafana_incident_enabled.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.16 on 2023-01-16 09:41 from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.AddField( model_name='organization', name='is_grafana_incident_enabled', diff --git a/engine/requirements.txt b/engine/requirements.txt index ace5bb9c..08e382f5 100644 --- a/engine/requirements.txt +++ b/engine/requirements.txt @@ -1,4 +1,4 @@ -django==3.2.16 +django==3.2.17 djangorestframework==3.12.4 slackclient==1.3.0 whitenoise==5.3.0 @@ -41,6 +41,7 @@ psycopg2==2.9.3 emoji==1.7.0 regex==2021.11.2 psutil==5.9.4 +django-migration-linter==4.1.0 opentelemetry-instrumentation-celery==0.36b0 opentelemetry-instrumentation-pymysql==0.36b0 opentelemetry-instrumentation-wsgi==0.36b0 diff --git a/engine/settings/base.py b/engine/settings/base.py index 3e7bf3a3..39c2ad3d 100644 --- a/engine/settings/base.py +++ b/engine/settings/base.py @@ -219,6 +219,7 @@ INSTALLED_APPS = [ "debug_toolbar", "social_django", "polymorphic", + "django_migration_linter", "fcm_django", "django_dbconn_retry", ] @@ -654,6 +655,10 @@ if OSS_INSTALLATION: "args": (), } # noqa +MIGRATION_LINTER_OPTIONS = {"exclude_apps": ["social_django", "silk", "fcm_django"]} +# Run migrations linter on each `python manage.py makemigrations` +MIGRATION_LINTER_OVERRIDE_MAKEMIGRATIONS = True + PYROSCOPE_PROFILER_ENABLED = getenv_boolean("PYROSCOPE_PROFILER_ENABLED", default=False) if PYROSCOPE_PROFILER_ENABLED: import pyroscope diff --git a/grafana-plugin/src/style/vars.css b/grafana-plugin/src/style/vars.css index bb0f2853..9e9b920c 100644 --- a/grafana-plugin/src/style/vars.css +++ b/grafana-plugin/src/style/vars.css @@ -27,7 +27,7 @@ --primary-text-color: rgb(36, 41, 46); --secondary-text-color: rgba(36, 41, 46, 0.75); --disabled-text-color: rgba(36, 41, 46, 0.5); - --warning-text-color: #8a6c00; + --warning-text-color: rgb(255, 120, 10); --success-text-color: rgb(10, 118, 78); --error-text-color: rgb(207, 14, 91); --primary-text-link: #1f62e0; @@ -56,7 +56,7 @@ --primary-text-color: rgb(204, 204, 220); --secondary-text-color: rgba(204, 204, 220, 0.65); --disabled-text-color: rgba(204, 204, 220, 0.4); - --warning-text-color: #f8d06b; + --warning-text-color: rgb(255, 120, 10); --success-text-color: rgb(108, 207, 142); --error-text-color: rgb(255, 82, 134); --primary-text-link: #6e9fff;