2023-05-24 14:27:48 +08:00
|
|
|
from dataclasses import asdict
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2023-07-12 08:07:45 +02:00
|
|
|
from rest_framework import serializers
|
2022-06-03 08:09:47 -06:00
|
|
|
|
feat: convert `organization.general_log_channel_id` to `organization.default_slack_channel` (#5191)
# What this PR does
Related to https://github.com/grafana/oncall-private/issues/2947
Right now `general_log_channel_id` is just a string value representing
the Slack Channel ID (ex. `C043HQ70QMB`). This PR migrates this instead
to be a foreign key relationship on the `slack_slackchannel` table and
updates all references to `general_log_channel_id`.
Tested migrations locally:
```bash
Operations to perform:
Apply all migrations: [redacted secret grafana-admin-creds:admin-user], alerts, auth, auth_token, base, contenttypes, email, exotel, fcm_django, google, heartbeat, labels, mobile_app, oss_installation, phone_notifications, schedules, sessions, slack, social_django, telegram, twilioapp, user_management, webhooks, zvonok
Running migrations:
Applying user_management.0024_organization_general_log_slack_channel... OK
source=engine:app google_trace_id=none logger=apps.user_management.migrations.0025_auto_20241017_1919 Starting migration to populate general_log_slack_channel field.
source=engine:app google_trace_id=none logger=apps.user_management.migrations.0025_auto_20241017_1919 Total organizations to process: 1
source=engine:app google_trace_id=none logger=apps.user_management.migrations.0025_auto_20241017_1919 Organization 1 updated with SlackChannel 2 (slack_id: C043LL6RTS7).
source=engine:app google_trace_id=none logger=apps.user_management.migrations.0025_auto_20241017_1919 Finished migration. Total organizations processed: 1. Organizations updated: 1. Missing SlackChannels: 0.
Applying user_management.0025_auto_20241017_1919... OK
```
## Future incoming PRs
- Drop `Organization.general_log_channel_id` column
- Migrate `ChannelFilter.slack_channel_id` and
`ResolutionNoteSlackMessage.slack_channel_id` to use foreign key
relationships
## Checklist
- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
show up in the autogenerated release notes.
2024-11-01 06:41:38 +01:00
|
|
|
from apps.api.serializers.slack_channel import SlackChannelSerializer
|
2024-04-11 11:51:56 -03:00
|
|
|
from apps.base.messaging import get_messaging_backend_from_id
|
2022-06-03 08:09:47 -06:00
|
|
|
from apps.base.models import LiveSetting
|
2023-05-24 14:27:48 +08:00
|
|
|
from apps.phone_notifications.phone_provider import get_phone_provider
|
2022-06-03 08:09:47 -06:00
|
|
|
from apps.slack.models import SlackTeamIdentity
|
|
|
|
|
from apps.user_management.models import Organization
|
|
|
|
|
from common.api_helpers.mixins import EagerLoadingMixin
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FastSlackTeamIdentitySerializer(serializers.ModelSerializer):
|
|
|
|
|
class Meta:
|
|
|
|
|
model = SlackTeamIdentity
|
2024-07-19 12:53:06 +01:00
|
|
|
fields = ["cached_name", "needs_reinstall"]
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class OrganizationSerializer(EagerLoadingMixin, serializers.ModelSerializer):
|
|
|
|
|
pk = serializers.CharField(read_only=True, source="public_primary_key")
|
|
|
|
|
slack_team_identity = FastSlackTeamIdentitySerializer(read_only=True)
|
|
|
|
|
|
|
|
|
|
name = serializers.CharField(required=False, allow_null=True, allow_blank=True, source="org_title")
|
2024-11-05 04:51:04 -05:00
|
|
|
slack_channel = SlackChannelSerializer(read_only=True, source="default_slack_channel")
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2023-08-11 15:37:44 +02:00
|
|
|
rbac_enabled = serializers.BooleanField(read_only=True, source="is_rbac_permissions_enabled")
|
2024-03-27 17:46:19 -04:00
|
|
|
grafana_incident_enabled = serializers.BooleanField(read_only=True, source="is_grafana_incident_enabled")
|
2024-11-05 05:54:38 -05:00
|
|
|
grafana_irm_enabled = serializers.BooleanField(read_only=True, source="is_grafana_irm_enabled")
|
2023-07-21 17:38:58 +02:00
|
|
|
|
feat: convert `schedule.channel` (char field) to `schedule.slack_channel` (foreign key) (#5199)
# What this PR does
`OnCallSchedule` equivalent of
https://github.com/grafana/oncall/pull/5191.
**NOTE**: merge after https://github.com/grafana/oncall/pull/5224 (so
that I can use some of the new serializer fields defined in there)
### Migration
```bash
Running migrations: │
│ source=engine:app google_trace_id=none logger=apps.schedules.migrations.0019_auto_20241021_1735 Starting migration to populate slack_channel field. │
│ source=engine:app google_trace_id=none logger=apps.schedules.migrations.0019_auto_20241021_1735 Total schedules to process: 1 │
│ source=engine:app google_trace_id=none logger=apps.schedules.migrations.0019_auto_20241021_1735 Schedule 26 updated with SlackChannel 2 (slack_id: C043LL6RTS7). │
│ source=engine:app google_trace_id=none logger=apps.schedules.migrations.0019_auto_20241021_1735 Bulk updated 1 OnCallSchedules with their Slack channel. │
│ source=engine:app google_trace_id=none logger=apps.schedules.migrations.0019_auto_20241021_1735 Finished migration. Total schedules processed: 1. Schedules updated: 1. Missing SlackChannels: 0. │
│ Applying schedules.0019_auto_20241021_1735... OK
```
### Tested Public API
```txt
POST {{oncall_host}}/api/v1/schedules/
Authorization: {{oncall_api_key}}
Content-Type: application/json
{
"name": "Demo testy testy2",
"type": "web",
"time_zone": "America/Los_Angeles",
"slack": {
"channel_id": "C05PPLYN1U1"
}
}
HTTP/1.1 201 Created
Content-Type: application/json
Vary: Accept, Origin
Allow: GET, POST, HEAD, OPTIONS
X-Frame-Options: DENY
Content-Length: 198
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
{
"id": "SBBN73UTUTVCE",
"team_id": null,
"name": "Demo testy testy2",
"time_zone": "America/Los_Angeles",
"on_call_now": [],
"shifts": [],
"slack": {
"channel_id": "C05PPLYN1U1",
"user_group_id": null
},
"type": "web"
}
```
### Tested via UI (eg; internal API)
https://www.loom.com/share/e66bf3468b144dd782da5eb6e0bfd0af
## Checklist
- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
show up in the autogenerated release notes.
2024-11-04 14:27:21 -05:00
|
|
|
SELECT_RELATED = ["slack_team_identity", "slack_channel"]
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
model = Organization
|
|
|
|
|
fields = [
|
|
|
|
|
"pk",
|
|
|
|
|
"name",
|
2024-01-10 13:29:43 +01:00
|
|
|
"stack_slug",
|
2022-06-03 08:09:47 -06:00
|
|
|
"slack_team_identity",
|
|
|
|
|
"slack_channel",
|
2023-07-21 17:38:58 +02:00
|
|
|
"rbac_enabled",
|
2024-03-27 17:46:19 -04:00
|
|
|
"grafana_incident_enabled",
|
2024-11-05 05:54:38 -05:00
|
|
|
"grafana_irm_enabled",
|
2024-10-24 13:52:40 -03:00
|
|
|
"direct_paging_prefer_important_policy",
|
2022-06-03 08:09:47 -06:00
|
|
|
]
|
|
|
|
|
read_only_fields = [
|
2024-01-10 13:29:43 +01:00
|
|
|
"stack_slug",
|
2022-06-03 08:09:47 -06:00
|
|
|
"slack_team_identity",
|
2023-07-21 17:38:58 +02:00
|
|
|
"rbac_enabled",
|
2024-03-27 17:46:19 -04:00
|
|
|
"grafana_incident_enabled",
|
2024-11-05 05:54:38 -05:00
|
|
|
"grafana_irm_enabled",
|
2022-06-03 08:09:47 -06:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CurrentOrganizationSerializer(OrganizationSerializer):
|
|
|
|
|
env_status = serializers.SerializerMethodField()
|
|
|
|
|
banner = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
|
|
class Meta(OrganizationSerializer.Meta):
|
|
|
|
|
fields = [
|
|
|
|
|
*OrganizationSerializer.Meta.fields,
|
|
|
|
|
"is_resolution_note_required",
|
|
|
|
|
"env_status",
|
|
|
|
|
"banner",
|
|
|
|
|
]
|
|
|
|
|
read_only_fields = [
|
|
|
|
|
*OrganizationSerializer.Meta.read_only_fields,
|
|
|
|
|
"banner",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def get_banner(self, obj):
|
2023-07-25 10:43:23 +01:00
|
|
|
from apps.base.models import DynamicSetting
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
banner = DynamicSetting.objects.get_or_create(
|
|
|
|
|
name="banner",
|
|
|
|
|
defaults={"json_value": {"title": None, "body": None}},
|
|
|
|
|
)[0]
|
|
|
|
|
return banner.json_value
|
|
|
|
|
|
|
|
|
|
def get_env_status(self, obj):
|
2023-05-24 14:27:48 +08:00
|
|
|
# deprecated in favour of ConfigAPIView.
|
|
|
|
|
# All new env statuses should be added there
|
2022-06-03 08:09:47 -06:00
|
|
|
LiveSetting.populate_settings_if_needed()
|
|
|
|
|
|
|
|
|
|
telegram_configured = not LiveSetting.objects.filter(name__startswith="TELEGRAM", error__isnull=False).exists()
|
2023-05-24 14:27:48 +08:00
|
|
|
phone_provider_config = get_phone_provider().flags
|
2025-04-21 14:23:37 -03:00
|
|
|
mattermost_configured = not LiveSetting.objects.filter(
|
|
|
|
|
name__startswith="MATTERMOST", error__isnull=False
|
|
|
|
|
).exists()
|
2022-06-03 08:09:47 -06:00
|
|
|
return {
|
|
|
|
|
"telegram_configured": telegram_configured,
|
2023-05-24 14:27:48 +08:00
|
|
|
"phone_provider": asdict(phone_provider_config),
|
2025-04-21 14:23:37 -03:00
|
|
|
"mattermost_configured": mattermost_configured,
|
2022-06-03 08:09:47 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FastOrganizationSerializer(serializers.ModelSerializer):
|
|
|
|
|
pk = serializers.CharField(read_only=True, source="public_primary_key")
|
|
|
|
|
name = serializers.CharField(read_only=True, source="org_title")
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
model = Organization
|
|
|
|
|
fields = ["pk", "name"]
|
2024-04-11 11:51:56 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class CurrentOrganizationConfigChecksSerializer(serializers.ModelSerializer):
|
|
|
|
|
is_chatops_connected = serializers.SerializerMethodField()
|
|
|
|
|
is_integration_chatops_connected = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
model = Organization
|
|
|
|
|
fields = [
|
|
|
|
|
"is_chatops_connected",
|
|
|
|
|
"is_integration_chatops_connected",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def get_is_chatops_connected(self, obj):
|
|
|
|
|
msteams_backend = get_messaging_backend_from_id("MSTEAMS")
|
|
|
|
|
return bool(
|
|
|
|
|
obj.slack_team_identity_id is not None # slack is connected
|
|
|
|
|
or obj.telegram_channel.exists() # telegram is connected
|
|
|
|
|
or (msteams_backend and msteams_backend.is_configured_for_organization(obj)) # msteams is connected
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def get_is_integration_chatops_connected(self, obj):
|
|
|
|
|
return (
|
2024-04-23 13:34:44 -03:00
|
|
|
(
|
|
|
|
|
obj.slack_team_identity_id is not None
|
|
|
|
|
and obj.alert_receive_channels.filter(channel_filters__notify_in_slack=True).exists()
|
|
|
|
|
)
|
2024-04-11 11:51:56 -03:00
|
|
|
or obj.alert_receive_channels.filter(channel_filters__notify_in_telegram=True).exists()
|
|
|
|
|
or obj.alert_receive_channels.filter(channel_filters__notification_backends__MSTEAMS__enabled=True).exists()
|
|
|
|
|
)
|