-
Notifications
You must be signed in to change notification settings - Fork 555
fix(workflows): block stale change request commits that conflict with published changes #8072
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,10 @@ | |
| from features.versioning.models import EnvironmentFeatureVersion | ||
| from features.versioning.signals import environment_feature_version_published | ||
| from features.versioning.tasks import trigger_update_version_webhooks | ||
| from features.workflows.core.exceptions import ChangeRequestNotApprovedError | ||
| from features.workflows.core.exceptions import ( | ||
| ChangeRequestConflictError, | ||
| ChangeRequestNotApprovedError, | ||
| ) | ||
|
|
||
| if TYPE_CHECKING: | ||
| from features.workflows.core.models import ChangeRequest | ||
|
|
@@ -27,6 +30,7 @@ def commit(self, committed_by: "FFAdminUser") -> None: | |
| "Change request has not been approved by all required approvers." | ||
| ) | ||
|
|
||
| self._check_for_conflicts() | ||
| self._publish_feature_states() | ||
| self._publish_environment_feature_versions(committed_by) | ||
| self._publish_change_sets(committed_by) | ||
|
|
@@ -45,6 +49,27 @@ def commit(self, committed_by: "FFAdminUser") -> None: | |
|
|
||
| self.change_request.save() | ||
|
|
||
| def _check_for_conflicts(self) -> None: | ||
| # Stale detection already runs when a scheduled change set is published | ||
| # by the task processor (see ``publish_version_change_set``). Manual | ||
| # commits publish their change sets immediately and previously skipped | ||
| # this check, silently reverting any changes that were published after | ||
| # the change request was created. Run the same conflict check here so | ||
| # that an out-of-date commit is blocked rather than overwriting newer | ||
| # changes. | ||
| if self.change_request.ignore_conflicts: | ||
| return | ||
|
|
||
| now = timezone.now() | ||
| for change_set in self.change_request.change_sets.all(): | ||
| # Scheduled change sets are conflict-checked when their task fires, | ||
| # since further conflicts can appear before they go live. | ||
| is_scheduled = change_set.live_from and change_set.live_from >= now | ||
| if is_scheduled: | ||
| continue | ||
|
Comment on lines
+67
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Do not skip a change set due exactly now.
|
||
| if change_set.get_conflicts(): | ||
| raise ChangeRequestConflictError() | ||
|
|
||
| def _publish_feature_states(self) -> None: | ||
| now = timezone.now() | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Serialise conflict detection with publication.
Two commits can both pass
_check_for_conflicts()before either publishes, allowing the later publish to overwrite the first. Protect the check and synchronous writes with a shared environment-scoped database lock and transaction; ensure scheduled publishing uses that same lock.Based on learnings,
ATOMIC_REQUESTSis not enabled, so this service must establish the boundary itself.Source: Learnings