oncall-engine/engine/common/ordered_model/serializer.py
Vadim Stepanov 602ed535e3
Fix duplicate orders on routes and escalation policies (#2568)
# What this PR does

Fix duplicate `order` values for models `EscalationPolicy` and
`ChannelFilter` using the same approach as
https://github.com/grafana/oncall/pull/2278.

- Make internal API action `move_to_position` a part of
[OrderedModelViewSet](https://github.com/grafana/oncall/pull/2568/files#diff-eb62521ccbcb072d1bd2156adeadae3b5895bce6d0d54432a23db3948b0ada54R11-R34),
so all ordered model views use the same logic.
- Make public API serializers for ordered models inherit from
[OrderedModelSerializer](https://github.com/grafana/oncall/pull/2568/files#diff-d749f94af5a49adaf5074325cdfad10ddd5a52dbfd78b49582867ebb9c92fae5R6-R38),
so ordered model views are consistent with each other in public API.
- Remove `order` from plugin fronted, since it's not being used
anywhere. The frontend uses step indices & `move_to_position` action
instead.
- Make escalation snapshot use step indices instead of orders, since
orders are not guaranteed to be sequential (+fix a minor off-by-one bug)

## Which issue(s) this PR fixes

https://github.com/grafana/oncall-private/issues/1680

## 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-07-18 17:17:53 +00:00

69 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from rest_framework import serializers
from common.api_helpers.exceptions import BadRequest
class OrderedModelSerializer(serializers.ModelSerializer):
"""Ordered model serializer to be used in public API."""
position = serializers.IntegerField(required=False, source="order")
# manual_order=True is intended for use by Terraform provider only, and is not a documented feature.
manual_order = serializers.BooleanField(default=False, write_only=True)
class Meta:
fields = ["position", "manual_order"]
def create(self, validated_data):
# Remove "manual_order" and "order" fields from validated_data, so they are not passed to create method.
manual_order = validated_data.pop("manual_order")
order = validated_data.pop("order", None)
# Create the instance.
# Instances are always created at the end of the list, and then moved to the desired position by _adjust_order.
instance = super().create(validated_data)
# Adjust order of the instance if necessary.
if order is not None:
self._adjust_order(instance, manual_order, order, created=True)
return instance
def update(self, instance, validated_data):
# Remove "manual_order" and "order" fields from validated_data, so they are not passed to update method.
manual_order = validated_data.pop("manual_order")
order = validated_data.pop("order", None)
# Adjust order of the instance if necessary.
if order is not None:
self._adjust_order(instance, manual_order, order, created=False)
# Proceed with the update.
return super().update(instance, validated_data)
@staticmethod
def _adjust_order(instance, manual_order, order, created):
# Passing order=-1 means that the policy should be moved to the end of the list.
# Works only for public API but not for Terraform provider.
if order == -1 and not manual_order:
if created:
# The policy was just created, so it is already at the end of the list.
return
order = instance.max_order()
# max_order() can't be None here because at least one instance exists the one we are moving.
assert order is not None
# Check the order is in the valid range.
# https://docs.djangoproject.com/en/4.1/ref/models/fields/#positiveintegerfield
if order < 0 or order > 2147483647:
raise BadRequest(detail="Invalid value for position field")
# Orders are swapped instead of moved when using Terraform, because Terraform may issue concurrent requests
# to create / update / delete multiple policies. "Move to" operation is not deterministic in this case, and
# final order of policies may be different depending on the order in which requests are processed. On the other
# hand, the result of concurrent "swap" operations is deterministic and does not depend on the order in
# which requests are processed.
if manual_order:
instance.swap(order)
else:
instance.to(order)