This needs to be a separate task called by the view itself. This is because the OrderedModelBase class that the FeatureSegment model inherits from implicitly uses bulk update to move other feature segments out of the way. Given the implementation, it's not really possible (or perha
( # type: ignore[no-untyped-def]
previous_id_priority_pairs: typing.List[typing.Tuple[int, int]],
feature_segment_ids: typing.List[int],
user_id: int = None, # type: ignore[assignment]
master_api_key_id: int = None, # type: ignore[assignment]
changed_at: str = None, # type: ignore[assignment]
)
| 140 | |
| 141 | @register_task_handler() |
| 142 | def create_segment_priorities_changed_audit_log( # type: ignore[no-untyped-def] |
| 143 | previous_id_priority_pairs: typing.List[typing.Tuple[int, int]], |
| 144 | feature_segment_ids: typing.List[int], |
| 145 | user_id: int = None, # type: ignore[assignment] |
| 146 | master_api_key_id: int = None, # type: ignore[assignment] |
| 147 | changed_at: str = None, # type: ignore[assignment] |
| 148 | ): |
| 149 | """ |
| 150 | This needs to be a separate task called by the view itself. This is because the OrderedModelBase class |
| 151 | that the FeatureSegment model inherits from implicitly uses bulk update to move other feature segments |
| 152 | out of the way. |
| 153 | |
| 154 | Given the implementation, it's not really possible (or perhaps desirable) to determine which moves |
| 155 | were actually made by the user. The logic is such that it iterates over the list of priorities sent |
| 156 | by the client. Take for example 2 overrides: |
| 157 | |
| 158 | segment_a - priority 0 |
| 159 | segment_b - priority 1 |
| 160 | |
| 161 | A change to move segment_b to priority 0 will set the priority on segment_b to 0 (triggering a single |
| 162 | write to the database). segment_a will be moved from 0 -> 1 using bulk update. This seems fine as we |
| 163 | then know b was moved and don't really care that a was moved out of the way. If the user, however moves |
| 164 | segment_a to priority 1, the single save will be done on segment_b since it'll be first in the list and |
| 165 | segment_a will be bulk updated. |
| 166 | |
| 167 | This is a very simple case, and it likely gets more complicated / difficult to follow if there are more |
| 168 | than 2 overrides. |
| 169 | """ |
| 170 | |
| 171 | # TODO: use previous priorities to show what changed. |
| 172 | |
| 173 | from features.models import FeatureSegment |
| 174 | |
| 175 | feature_segments = FeatureSegment.objects.filter(id__in=feature_segment_ids) |
| 176 | if not feature_segments: |
| 177 | return |
| 178 | |
| 179 | # all feature segments should have the same value for feature, environment and |
| 180 | # environment feature version |
| 181 | environment = feature_segments[0].environment |
| 182 | feature = feature_segments[0].feature |
| 183 | environment_feature_version_id = feature_segments[0].environment_feature_version_id |
| 184 | |
| 185 | if environment_feature_version_id is not None: |
| 186 | # Don't create audit logs for FeatureSegments wrapped in a version |
| 187 | # as this is handled by the feature history instead. |
| 188 | return |
| 189 | |
| 190 | AuditLog.objects.create( |
| 191 | log=f"Segment overrides re-ordered for feature '{feature.name}'.", |
| 192 | environment=environment, |
| 193 | project_id=environment.project_id, |
| 194 | author_id=user_id, |
| 195 | related_object_id=feature.id, |
| 196 | related_object_type=RelatedObjectType.FEATURE.name, |
| 197 | master_api_key_id=master_api_key_id, |
| 198 | created_date=( |
| 199 | datetime.fromisoformat(changed_at) |
searching dependent graphs…