2022-06-03 08:09:47 -06:00
|
|
|
from rest_framework import serializers
|
|
|
|
|
|
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
|
|
|
from apps.api.serializers.slack_channel import SlackChannelSerializer
|
2022-06-03 08:09:47 -06:00
|
|
|
from apps.api.serializers.user_group import UserGroupSerializer
|
2025-01-16 09:19:16 -03:00
|
|
|
from apps.schedules.constants import SCHEDULE_CHECK_NEXT_DAYS
|
2022-06-23 13:46:04 +04:00
|
|
|
from apps.schedules.models import OnCallSchedule
|
2025-01-16 09:19:16 -03:00
|
|
|
from apps.schedules.tasks import (
|
|
|
|
|
check_gaps_and_empty_shifts_in_schedule,
|
|
|
|
|
schedule_notify_about_empty_shifts_in_schedule,
|
|
|
|
|
schedule_notify_about_gaps_in_schedule,
|
|
|
|
|
)
|
2022-06-03 08:09:47 -06:00
|
|
|
from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField
|
|
|
|
|
from common.api_helpers.mixins import EagerLoadingMixin
|
2023-12-07 15:44:52 +01:00
|
|
|
from common.api_helpers.utils import CurrentOrganizationDefault
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ScheduleBaseSerializer(EagerLoadingMixin, serializers.ModelSerializer):
|
|
|
|
|
id = serializers.CharField(read_only=True, source="public_primary_key")
|
|
|
|
|
organization = serializers.HiddenField(default=CurrentOrganizationDefault())
|
2023-12-07 15:44:52 +01:00
|
|
|
team = TeamPrimaryKeyRelatedField(allow_null=True, required=False)
|
2024-11-05 04:51:04 -05:00
|
|
|
slack_channel = SlackChannelSerializer(read_only=True)
|
2022-06-03 08:09:47 -06:00
|
|
|
user_group = UserGroupSerializer()
|
|
|
|
|
warnings = serializers.SerializerMethodField()
|
|
|
|
|
on_call_now = serializers.SerializerMethodField()
|
2022-09-05 15:00:14 -03:00
|
|
|
number_of_escalation_chains = serializers.SerializerMethodField()
|
2023-03-10 13:21:50 -03:00
|
|
|
enable_web_overrides = serializers.SerializerMethodField()
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
fields = [
|
|
|
|
|
"id",
|
|
|
|
|
"organization",
|
|
|
|
|
"team",
|
|
|
|
|
"name",
|
|
|
|
|
"user_group",
|
|
|
|
|
"warnings",
|
|
|
|
|
"on_call_now",
|
|
|
|
|
"has_gaps",
|
|
|
|
|
"notify_oncall_shift_freq",
|
|
|
|
|
"notify_empty_oncall",
|
|
|
|
|
"mention_oncall_start",
|
|
|
|
|
"mention_oncall_next",
|
2022-09-05 15:00:14 -03:00
|
|
|
"number_of_escalation_chains",
|
2023-03-10 13:21:50 -03:00
|
|
|
"enable_web_overrides",
|
2022-06-03 08:09:47 -06: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 = ["organization", "team", "user_group", "slack_channel"]
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
CANT_UPDATE_USER_GROUP_WARNING = (
|
|
|
|
|
"Cannot update the user group, make sure to grant user group modification rights to "
|
|
|
|
|
"non-admin users in Slack workspace settings"
|
|
|
|
|
)
|
2025-01-16 09:19:16 -03:00
|
|
|
SCHEDULE_HAS_GAPS_WARNING = f"Schedule has unassigned time periods during next {SCHEDULE_CHECK_NEXT_DAYS} days"
|
|
|
|
|
SCHEDULE_HAS_EMPTY_SHIFTS_WARNING = f"Schedule has empty shifts during next {SCHEDULE_CHECK_NEXT_DAYS} days"
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
def get_warnings(self, obj):
|
|
|
|
|
can_update_user_groups = self.context.get("can_update_user_groups", False)
|
|
|
|
|
warnings = []
|
|
|
|
|
if obj.user_group and not can_update_user_groups:
|
|
|
|
|
warnings.append(self.CANT_UPDATE_USER_GROUP_WARNING)
|
|
|
|
|
if obj.has_gaps:
|
|
|
|
|
warnings.append(self.SCHEDULE_HAS_GAPS_WARNING)
|
|
|
|
|
if obj.has_empty_shifts:
|
|
|
|
|
warnings.append(self.SCHEDULE_HAS_EMPTY_SHIFTS_WARNING)
|
|
|
|
|
return warnings
|
|
|
|
|
|
|
|
|
|
def get_on_call_now(self, obj):
|
2023-01-25 11:08:09 +01:00
|
|
|
# Serializer context is set here: apps.api.views.schedule.ScheduleView.get_serializer_context
|
Add responders improvements (#3128)
# What this PR does
https://www.loom.com/share/c5e10b5ec51343d0954c6f41cfd6a5fb
## Summary of backend changes
- Add `AlertReceiveChannel.get_orgs_direct_paging_integrations` method
and `AlertReceiveChannel.is_contactable` property. These are needed to
be able to (optionally) filter down teams, in the `GET /teams` internal
API endpoint
([here](https://github.com/grafana/oncall/pull/3128/files#diff-a4bd76e557f7e11dafb28a52c1034c075028c693b3c12d702d53c07fc6f24c05R55-R63)),
to just teams that have a "contactable" Direct Paging integration
- `engine/apps/alerts/paging.py`
- update these functions to support new UX. In short `direct_paging` no
longer takes a list of `ScheduleNotifications` or an `EscalationChain`
object
- add `user_is_oncall` helper function
- add `_construct_title` helper function. In short if no `title` is
provided, which is the case for Direct Pages originating from OnCall
(either UI or Slack), then the format is `f"{from_user.username} is
paging <team.name (if team is specified> <comma separated list of
user.usernames> to join escalation"`
- `engine/apps/api/serializers/team.py` - add
`number_of_users_currently_oncall` attribute to response schema
([code](https://github.com/grafana/oncall/pull/3128/files#diff-26af48f796c9e987a76447586dd0f92349783d6ea6a0b6039a2f0f28bd58c2ebR45-R52))
- `engine/apps/api/serializers/user.py` - add `is_currently_oncall`
attribute to response schema
([code](https://github.com/grafana/oncall/pull/3128/files#diff-6744b5544ebb120437af98a996da5ad7d48ee1139a6112c7e3904010ab98f232R157-R162))
- `engine/apps/api/views/team.py` - add support for two new optional
query params `only_include_notifiable_teams` and `include_no_team`
([code](https://github.com/grafana/oncall/pull/3128/files#diff-a4bd76e557f7e11dafb28a52c1034c075028c693b3c12d702d53c07fc6f24c05R55-R70))
- `engine/apps/api/views/user.py`
- in the `GET /users` internal API endpoint, when specifying the
`search` query param now also search on `teams__name`
([code](https://github.com/grafana/oncall/pull/3128/files#diff-30309629484ad28e6fe09816e1bd226226d652ea977b6f3b6775976c729bf4b5R223);
this is a new UX requirement)
- add support for a new optional query param, `is_currently_oncall`, to
allow filtering users based on.. whether they are currently on call or
not
([code](https://github.com/grafana/oncall/pull/3128/files#diff-30309629484ad28e6fe09816e1bd226226d652ea977b6f3b6775976c729bf4b5R272-R282))
- remove `check_availability` endpoint (no longer used with new UX; also
removed references in frontend code)
- `engine/apps/slack/scenarios/paging.py` and
`engine/apps/slack/scenarios/manage_responders.py` - update Slack
workflows to support new UX. Schedules are no longer a concept here.
When creating a new alert group via `/escalate` the user either
specifies a team and/or user(s) (they must specify at least one of the
two and validation is done here to check this). When adding responders
to an existing alert group it's simply a list of users that they can
add, no more schedules.
- add `Organization.slack_is_configured` and
`Organization.telegram_is_configured` properties. These are needed to
support [this new functionality
](https://github.com/grafana/oncall/pull/3128/files#diff-9d96504027309f2bd1e95352bac1433b09b60eb4fafb611b52a6c15ed16cbc48R271-R272)
in the `AlertReceiveChannel` model.
## Summary of frontend changes
- Refactor/rename `EscalationVariants` component to `AddResponders` +
remove `grafana-plugin/src/containers/UserWarningModal` (no longer
needed with new UX)
- Remove `grafana-plugin/src/models/user.ts` as it seemed to be a
duplicate of `grafana-plugin/src/models/user/user.types.ts`
Related to https://github.com/grafana/incident/issues/4278
- Closes #3115
- Closes #3116
- Closes #3117
- Closes #3118
- Closes #3177
## TODO
- [x] make frontend changes
- [x] update Slack backend functionality
- [x] update public documentation
- [x] add/update e2e tests
## Post-deploy To-dos
- [ ] update dev/ops/production Slack bots to update `/escalate` command
description (should now say "Direct page a team or user(s)")
## 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] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-10-27 12:12:07 -04:00
|
|
|
users = self.context["oncall_users"].get(obj, [])
|
2024-08-15 16:20:55 +02:00
|
|
|
organization = self.context["request"].auth.organization
|
|
|
|
|
return [user.short(organization) for user in users]
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2022-09-05 15:00:14 -03:00
|
|
|
def get_number_of_escalation_chains(self, obj):
|
|
|
|
|
# num_escalation_chains param added in queryset via annotate. Check ScheduleView.get_queryset
|
|
|
|
|
# return 0 for just created schedules
|
2022-10-19 16:34:39 -03:00
|
|
|
num = getattr(obj, "num_escalation_chains", 0)
|
2022-10-20 12:57:59 -03:00
|
|
|
return num or 0
|
2022-09-05 15:00:14 -03:00
|
|
|
|
2023-03-10 13:21:50 -03:00
|
|
|
def get_enable_web_overrides(self, obj):
|
|
|
|
|
return False
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
def validate(self, attrs):
|
|
|
|
|
if "slack_channel_id" in attrs:
|
2024-11-05 04:51:04 -05:00
|
|
|
# this is set in the serializer classes which subclass ScheduleBaseSerializer
|
|
|
|
|
attrs["slack_channel"] = attrs.pop("slack_channel_id", None)
|
2022-06-03 08:09:47 -06:00
|
|
|
return attrs
|
|
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
|
created_schedule = super().create(validated_data)
|
2025-01-16 09:19:16 -03:00
|
|
|
check_gaps_and_empty_shifts_in_schedule.apply_async((created_schedule.pk,))
|
2022-06-03 08:09:47 -06:00
|
|
|
schedule_notify_about_empty_shifts_in_schedule.apply_async((created_schedule.pk,))
|
|
|
|
|
schedule_notify_about_gaps_in_schedule.apply_async((created_schedule.pk,))
|
|
|
|
|
return created_schedule
|
2022-06-23 13:46:04 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ScheduleFastSerializer(serializers.ModelSerializer):
|
|
|
|
|
id = serializers.CharField(read_only=True, source="public_primary_key")
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
model = OnCallSchedule
|
|
|
|
|
fields = [
|
|
|
|
|
"id",
|
|
|
|
|
"name",
|
|
|
|
|
]
|