Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import {useCanEditDetector} from 'sentry/views/detectors/utils/useCanEditDetector';

export function DisableDetectorAction({detector}: {detector: Detector}) {
const organization = useOrganization();
const {mutate: updateDetector, isPending: isUpdating} = useUpdateDetector();

const toggleDisabled = useCallback(() => {
Expand All @@ -50,10 +51,29 @@ export function DisableDetectorAction({detector}: {detector: Detector}) {
return null;
}

// Check if this is an anomaly detection detector without the required feature
const isAnomalyDetector =
detector.type === 'metric_issue' &&
'config' in detector &&
detector.config?.detectionType === 'dynamic';
const hasAnomalyDetectionFeature = organization.features.includes(
'anomaly-detection-alerts'
);
const requiresUpgrade = isAnomalyDetector && !hasAnomalyDetectionFeature;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated anomaly detector check logic across files

Low Severity

The logic for isAnomalyDetector, hasAnomalyDetectionFeature, and requiresUpgrade is identically duplicated in both actions.tsx and disabledAlert.tsx. If the feature flag name 'anomaly-detection-alerts' or the detection type check ever needs to change, it would require updating both files separately, risking inconsistency. This logic could be extracted into a shared utility function in the utils directory, similar to the existing anomalyDetectionLabels.tsx utilities.

Additional Locations (1)

Fix in Cursor Fix in Web


// If it's a metric detector without the feature, disable the Enable button
const isButtonDisabled = isUpdating || (requiresUpgrade && !detector.enabled);
const tooltipText =
requiresUpgrade && !detector.enabled
? t('Anomaly detection is only available on Business and Enterprise plans')
: undefined;

return (
<Button size="sm" onClick={toggleDisabled} disabled={isUpdating}>
{detector.enabled ? t('Disable') : t('Enable')}
</Button>
<Tooltip title={tooltipText} disabled={!tooltipText}>
<Button size="sm" onClick={toggleDisabled} disabled={isButtonDisabled}>
{detector.enabled ? t('Disable') : t('Enable')}
</Button>
</Tooltip>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {UptimeDetectorFixture} from 'sentry-fixture/detectors';
import {MetricDetectorFixture, UptimeDetectorFixture} from 'sentry-fixture/detectors';
import {OrganizationFixture} from 'sentry-fixture/organization';
import {ProjectFixture} from 'sentry-fixture/project';

Expand Down Expand Up @@ -74,4 +74,27 @@ describe('DisabledAlert', () => {
);
});
});

it('shows upgrade message for disabled anomaly detector without feature', () => {
const orgWithoutFeature = OrganizationFixture({
access: ['org:write', 'alerts:write'],
features: [],
});
const anomalyDetector = MetricDetectorFixture({
enabled: false,
projectId: project.id,
config: {detectionType: 'dynamic'},
});

render(<DisabledAlert detector={anomalyDetector} message="Test message" />, {
organization: orgWithoutFeature,
});

expect(
screen.getByText(
/Anomaly detection is only available on Business and Enterprise plans/
)
).toBeInTheDocument();
expect(screen.queryByRole('button', {name: 'Enable'})).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {Alert} from 'sentry/components/core/alert';
import {Button} from 'sentry/components/core/button';
import ExternalLink from 'sentry/components/links/externalLink';
import {IconPlay} from 'sentry/icons';
import {t} from 'sentry/locale';
import {t, tct} from 'sentry/locale';
import type {Detector} from 'sentry/types/workflowEngine/detectors';
import useOrganization from 'sentry/utils/useOrganization';
import {useUpdateDetector} from 'sentry/views/detectors/hooks';
import {useCanEditDetector} from 'sentry/views/detectors/utils/useCanEditDetector';

Expand All @@ -17,6 +19,7 @@ type DisabledAlertProps = {
* to enable it. The alert automatically hides when the detector is enabled.
*/
export function DisabledAlert({detector, message}: DisabledAlertProps) {
const organization = useOrganization();
const {mutate: updateDetector, isPending: isEnabling} = useUpdateDetector();
const canEdit = useCanEditDetector({
detectorType: detector.type,
Expand All @@ -31,6 +34,33 @@ export function DisabledAlert({detector, message}: DisabledAlertProps) {
updateDetector({detectorId: detector.id, enabled: true});
};

// Check if this is an anomaly detection detector without the required feature
const isAnomalyDetector =
detector.type === 'metric_issue' &&
'config' in detector &&
detector.config?.detectionType === 'dynamic';
const hasAnomalyDetectionFeature = organization.features.includes(
'anomaly-detection-alerts'
);
const requiresUpgrade = isAnomalyDetector && !hasAnomalyDetectionFeature;

if (requiresUpgrade) {
return (
<Alert.Container>
<Alert variant="muted">
{tct(
'Anomaly detection is only available on Business and Enterprise plans. [link:Upgrade your plan] to enable this monitor.',
{
link: (
<ExternalLink href="https://sentry.io/pricing/?referrer=anomaly-detection" />
),
}
)}
</Alert>
</Alert.Container>
);
}

return (
<Alert.Container>
<Alert
Expand Down
Loading