-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
feat(uptime): Add billing seat management for detector validators #102989
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: master
Are you sure you want to change the base?
feat(uptime): Add billing seat management for detector validators #102989
Conversation
| if detector.enabled: | ||
| try: | ||
| enable_uptime_detector(detector, ensure_assignment=True) | ||
| except UptimeMonitorNoSeatAvailable: | ||
| # No need to do anything if we failed to handle seat | ||
| # assignment. The monitor will be created, but not enabled | ||
| pass | ||
| else: | ||
| disable_uptime_detector(detector) | ||
|
|
||
| return detector |
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.
Bug: Detector creation ignores enabled=False in validated_data, always defaulting to True due to db_default.
Severity: CRITICAL | Confidence: 0.95
🔍 Detailed Analysis
When creating a new detector via the API, the Detector model's enabled field defaults to True due to db_default=True. The BaseDetectorTypeValidator.create() method does not explicitly pass the enabled value from validated_data to the Detector constructor. Consequently, if a user requests {"enabled": False, ...}, the UptimeDomainCheckFailureValidator.create() method will incorrectly call enable_uptime_detector() because detector.enabled will always be True after creation, ignoring the user's intent.
💡 Suggested Fix
Ensure the enabled field from validated_data is explicitly passed to the Detector constructor in BaseDetectorTypeValidator.create() to override the database default when a value is provided.
🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/sentry/uptime/endpoints/validators.py#L469-L482
Potential issue: When creating a new detector via the API, the `Detector` model's
`enabled` field defaults to `True` due to `db_default=True`. The
`BaseDetectorTypeValidator.create()` method does not explicitly pass the `enabled` value
from `validated_data` to the `Detector` constructor. Consequently, if a user requests
`{"enabled": False, ...}`, the `UptimeDomainCheckFailureValidator.create()` method will
incorrectly call `enable_uptime_detector()` because `detector.enabled` will always be
`True` after creation, ignoring the user's intent.
Did we get this right? 👍 / 👎 to inform future reviews.
|
|
||
| if was_enabled != enabled: | ||
| if enabled: | ||
| try: | ||
| enable_uptime_detector(instance) | ||
| except UptimeMonitorNoSeatAvailable as err: | ||
| raise serializers.ValidationError({"enabled": [err.result.reason]}) | ||
| else: | ||
| disable_uptime_detector(instance) | ||
|
|
||
| super().update(instance, validated_data) |
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.
Bug: Detector enabled state is toggled twice during update, once by child and once by parent class.
Severity: HIGH | Confidence: 0.95
🔍 Detailed Analysis
In UptimeDomainCheckFailureValidator.update(), the detector's enabled state is toggled twice. First, the child class explicitly calls enable_uptime_detector() or disable_uptime_detector() based on the enabled state. Subsequently, super().update(instance, validated_data) is called. The parent class's update() method then re-processes the enabled field from validated_data and calls toggle_detector(), leading to redundant state changes. This can result in inconsistent states if the two toggle mechanisms have different behaviors.
💡 Suggested Fix
Remove the enabled field from validated_data before calling super().update() in UptimeDomainCheckFailureValidator.update() to prevent the parent class from re-processing it.
🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/sentry/uptime/endpoints/validators.py#L485-L498
Potential issue: In `UptimeDomainCheckFailureValidator.update()`, the detector's
`enabled` state is toggled twice. First, the child class explicitly calls
`enable_uptime_detector()` or `disable_uptime_detector()` based on the `enabled` state.
Subsequently, `super().update(instance, validated_data)` is called. The parent class's
`update()` method then re-processes the `enabled` field from `validated_data` and calls
`toggle_detector()`, leading to redundant state changes. This can result in inconsistent
states if the two toggle mechanisms have different behaviors.
Did we get this right? 👍 / 👎 to inform future reviews.
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.
Yeah this is true, but it's not really a problem and I think it keeps this logic easier to maintain.
saponifi3d
left a comment
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.
lgtm from the workflow engine perspective, might want another review from someone with more context over the other changes.
| uptime_subscription = get_uptime_subscription(self.instance) | ||
| delete_uptime_subscription(uptime_subscription) | ||
|
|
||
| super().delete() |
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.
👀 yeah, this does feel a bit cleaner than having the hooks -- yay it's just one surface area now! 🎉
b333159 to
674fb06
Compare
| # assignment. The monitor will be created, but not enabled | ||
| pass | ||
|
|
||
| return detector |
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.
Bug: Disabled Features Still Activate
The create method unconditionally calls enable_uptime_detector regardless of the enabled value in validated_data. If a detector is created with enabled=False, it will still attempt to enable it and assign a seat, contradicting the user's intent and potentially consuming quota unnecessarily.
Add billing seat assignment and removal to UptimeDomainCheckFailureValidator to ensure uptime detectors created, updated, or deleted via the detector APIs correctly handle billing seats. Changes: - Assign seats when creating enabled detectors (gracefully disable if no seats) - Assign/remove seats when enabling/disabling detectors via updates - Remove seats immediately when deleting detectors - Raise validation errors when enabling fails due to no available seats Also add comprehensive tests to verify all billing seat operations work correctly across create, update, and delete operations. Fixes [NEW-527: Ensure CRUD / enable+disable detectors in new UI handles assigning / unassigning seats](https://linear.app/getsentry/issue/NEW-527/ensure-crud-enabledisable-detectors-in-new-ui-handles-assigning)
674fb06 to
405c673
Compare
| enable_uptime_detector(instance) | ||
| else: | ||
| disable_uptime_detector(instance) | ||
|
|
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.
Bug: Conflicting Updates Lead to Detector Instability
The update method calls enable_uptime_detector or disable_uptime_detector which update detector.enabled, then immediately calls super().update(validated_data) which also updates detector.enabled via toggle_detector. This double update creates a race condition and potential state inconsistency because toggle_detector also modifies detector.status, which may conflict with the status management in the uptime-specific enable/disable functions. The enabled field should be removed from validated_data before calling super().update() to prevent the base class from re-processing it.
| detector = super().create(validated_data) | ||
|
|
||
| try: | ||
| enable_uptime_detector(detector, ensure_assignment=True) |
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.
Do we need to worry about mode != UptimeMonitorMode.AUTO_DETECTED_ONBOARDING and creating disabled Detectors? or are those not a possible for this?
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.
Yeah not applicable here. These will always be created in manual mode.
brendanhsentry
left a comment
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.
nice looks good to me
Add billing seat assignment and removal to UptimeDomainCheckFailureValidator
to ensure uptime detectors created, updated, or deleted via the detector APIs
correctly handle billing seats.
Changes:
Also add comprehensive tests to verify all billing seat operations work
correctly across create, update, and delete operations.
Fixes NEW-527: Ensure CRUD / enable+disable detectors in new UI handles assigning / unassigning seats