Skip to content
Draft
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 @@ -324,7 +324,6 @@ export function GlobalCommandPaletteActions() {
const hasInsightsRollout = organization.features.includes(
'insights-to-dashboards-ui-rollout'
);
const hasWorkflowEngineUI = organization.features.includes('workflow-engine-ui');
const hasPrebuiltDashboards = organization.features.includes(
'dashboards-prebuilt-insights-dashboards'
);
Expand Down Expand Up @@ -494,111 +493,89 @@ export function GlobalCommandPaletteActions() {
</CMDKAction>
</CMDKAction>

{/* Hide the entire Insights section only when both migrations are active.
During partial rollout, individual items are gated: domain links
(Frontend, Backend, etc.) by insights-to-dashboards-ui-rollout,
and Crons/Uptime by workflow-engine-ui. */}
{organization.features.includes('performance-view') &&
!(hasInsightsRollout && hasWorkflowEngineUI) && (
<CMDKAction
display={{
label: t('Insights'),
icon: <IconGraph type="area" />,
}}
limit={4}
>
{!hasInsightsRollout && (
<CMDKAction
display={{label: t('Frontend')}}
keywords={[t('apdex'), t('web vitals'), t('performance score')]}
to={`${prefix}/insights/${FRONTEND_LANDING_SUB_PATH}/`}
/>
)}
{!hasInsightsRollout && (
<CMDKAction
display={{label: t('Backend')}}
to={`${prefix}/insights/${BACKEND_LANDING_SUB_PATH}/`}
/>
)}
{!hasInsightsRollout && (
<CMDKAction
display={{label: t('Mobile')}}
to={`${prefix}/insights/${MOBILE_LANDING_SUB_PATH}/`}
/>
)}
{!hasInsightsRollout && (
<CMDKAction
display={{label: t('Agents')}}
to={`${prefix}/insights/${AGENTS_LANDING_SUB_PATH}/`}
/>
)}
{!hasInsightsRollout && (
<CMDKAction
display={{label: t('MCP')}}
to={`${prefix}/insights/${MCP_LANDING_SUB_PATH}/`}
/>
)}
{!hasWorkflowEngineUI && (
<CMDKAction
display={{label: t('Crons')}}
keywords={[t('jobs'), t('cron jobs')]}
to={`${prefix}/insights/crons/`}
/>
)}
{organization.features.includes('uptime') && !hasWorkflowEngineUI && (
<CMDKAction
display={{label: t('Uptime')}}
keywords={[t('uptime monitors')]}
to={`${prefix}/insights/uptime/`}
/>
)}
{!hasInsightsRollout && (
<CMDKAction
display={{label: t('Projects')}}
to={`${prefix}/insights/projects/`}
/>
)}
</CMDKAction>
)}

{hasWorkflowEngineUI && (
<CMDKAction display={{label: t('Monitors'), icon: <IconSiren />}} limit={4}>
<CMDKAction display={{label: t('All Monitors')}} to={`${prefix}/monitors/`} />
<CMDKAction
display={{label: t('My Monitors')}}
to={`${prefix}/monitors/my-monitors/`}
/>
<CMDKAction display={{label: t('Error')}} to={`${prefix}/monitors/errors/`} />
<CMDKAction
display={{label: t('Metric')}}
to={`${prefix}/monitors/metrics/`}
/>
<CMDKAction
display={{label: t('Cron')}}
keywords={[t('jobs'), t('cron jobs')]}
to={`${prefix}/monitors/crons/`}
/>
{organization.features.includes('uptime') && (
{/* Hide the Insights section once the insights-to-dashboards migration
is active; Crons and Uptime now live under the Monitors section. */}
{organization.features.includes('performance-view') && !hasInsightsRollout && (
<CMDKAction
display={{
label: t('Insights'),
icon: <IconGraph type="area" />,
}}
limit={4}
>
{!hasInsightsRollout && (
<CMDKAction
display={{label: t('Uptime')}}
keywords={[t('uptime monitors'), t('monitors')]}
to={`${prefix}/monitors/uptime/`}
display={{label: t('Frontend')}}
keywords={[t('apdex'), t('web vitals'), t('performance score')]}
to={`${prefix}/insights/${FRONTEND_LANDING_SUB_PATH}/`}
/>
)}
{organization.features.includes('preprod-size-monitors-frontend') && (
{!hasInsightsRollout && (
<CMDKAction
display={{label: t('Mobile Build')}}
to={`${prefix}/monitors/mobile-builds/`}
display={{label: t('Backend')}}
to={`${prefix}/insights/${BACKEND_LANDING_SUB_PATH}/`}
/>
)}
{!hasInsightsRollout && (
<CMDKAction
display={{label: t('Mobile')}}
to={`${prefix}/insights/${MOBILE_LANDING_SUB_PATH}/`}
/>
)}
{!hasInsightsRollout && (
<CMDKAction
display={{label: t('Agents')}}
to={`${prefix}/insights/${AGENTS_LANDING_SUB_PATH}/`}
/>
)}
{!hasInsightsRollout && (
<CMDKAction
display={{label: t('MCP')}}
to={`${prefix}/insights/${MCP_LANDING_SUB_PATH}/`}
/>
)}
{!hasInsightsRollout && (
<CMDKAction
display={{label: t('Projects')}}
to={`${prefix}/insights/projects/`}
/>
)}
<CMDKAction
display={{label: t('Alerts')}}
keywords={[t('alert rules'), t('issue alert')]}
to={`${prefix}/monitors/alerts/`}
/>
</CMDKAction>
)}

<CMDKAction display={{label: t('Monitors'), icon: <IconSiren />}} limit={4}>
<CMDKAction display={{label: t('All Monitors')}} to={`${prefix}/monitors/`} />
<CMDKAction
display={{label: t('My Monitors')}}
to={`${prefix}/monitors/my-monitors/`}
/>
<CMDKAction display={{label: t('Error')}} to={`${prefix}/monitors/errors/`} />
<CMDKAction display={{label: t('Metric')}} to={`${prefix}/monitors/metrics/`} />
<CMDKAction
display={{label: t('Cron')}}
keywords={[t('jobs'), t('cron jobs')]}
to={`${prefix}/monitors/crons/`}
/>
{organization.features.includes('uptime') && (
<CMDKAction
display={{label: t('Uptime')}}
keywords={[t('uptime monitors'), t('monitors')]}
to={`${prefix}/monitors/uptime/`}
/>
)}
{organization.features.includes('preprod-size-monitors-frontend') && (
<CMDKAction
display={{label: t('Mobile Build')}}
to={`${prefix}/monitors/mobile-builds/`}
/>
)}
<CMDKAction
display={{label: t('Alerts')}}
keywords={[t('alert rules'), t('issue alert')]}
to={`${prefix}/monitors/alerts/`}
/>
</CMDKAction>

<CMDKAction display={{label: t('Settings'), icon: <IconSettings />}} limit={4}>
{visibleOrgSettingsNavItems.map(item => (
<CMDKAction
Expand Down
46 changes: 1 addition & 45 deletions static/app/views/issueDetails/sidebar/detectorSection.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {

describe('DetectorSection', () => {
const detectorId = '123';
const organization = OrganizationFixture({features: ['workflow-engine-ui']});
const organization = OrganizationFixture();
const project = ProjectFixture();
const issueDetailsContext = {
sectionData: {},
Expand Down Expand Up @@ -185,48 +185,4 @@ describe('DetectorSection', () => {
screen.getByText('This issue was created by an uptime monitoring alert rule.')
).toBeInTheDocument();
});

it('links to metric alert rule details when workflow engine UI is disabled', async () => {
const alertRuleId = 456;
const event = EventFixture({
occurrence: {
evidenceData: {
detectorId,
},
type: 8001,
},
});
const group = GroupFixture({
issueCategory: IssueCategory.METRIC,
issueType: IssueType.METRIC_ISSUE,
});
const orgWithOnlyMetricIssues = OrganizationFixture();
const metricDetector = MetricDetectorFixture({
id: detectorId,
alertRuleId,
});
const detectorDetails = getDetectorDetails({
event,
organization: orgWithOnlyMetricIssues,
project,
});

MockApiClient.addMockResponse({
url: `/organizations/${orgWithOnlyMetricIssues.slug}/detectors/${detectorId}/`,
body: metricDetector,
});

render(
<IssueDetailsContext value={{...issueDetailsContext, detectorDetails}}>
<DetectorSection group={group} project={project} />
</IssueDetailsContext>,
{organization: orgWithOnlyMetricIssues}
);

const link = await screen.findByRole('button', {name: 'View metric alert details'});
expect(link).toHaveAttribute(
'href',
`/organizations/${orgWithOnlyMetricIssues.slug}/issues/alerts/rules/details/${alertRuleId}/`
);
});
});
51 changes: 1 addition & 50 deletions static/app/views/issueDetails/sidebar/detectorSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@ import styled from '@emotion/styled';

import {LinkButton} from '@sentry/scraps/button';

import {LoadingError} from 'sentry/components/loadingError';
import {t} from 'sentry/locale';
import type {Event} from 'sentry/types/event';
import type {Group} from 'sentry/types/group';
import type {Organization} from 'sentry/types/organization';
import type {Project} from 'sentry/types/project';
import type {MetricDetector} from 'sentry/types/workflowEngine/detectors';
import {defined} from 'sentry/utils/defined';
import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
import {useOrganization} from 'sentry/utils/useOrganization';
import {makeAlertsPathname} from 'sentry/views/alerts/pathnames';
import {useDetectorQuery} from 'sentry/views/detectors/hooks';
import {makeMonitorDetailsPathname} from 'sentry/views/detectors/pathnames';
import {useIssueDetails} from 'sentry/views/issueDetails/context';
import type {DetectorDetails} from 'sentry/views/issueDetails/sidebar/detectorDetails';
Expand Down Expand Up @@ -100,19 +95,11 @@ export function getDetectorDetails({

export function DetectorSection({group, project}: {group: Group; project: Project}) {
const issueConfig = getConfigForIssueType(group, project);
const organization = useOrganization();
const {detectorDetails} = useIssueDetails();
const {detectorPath, description, detectorId, detectorType} = detectorDetails;
const {detectorPath, description} = detectorDetails;
const detectorCtaText = issueConfig.detector.ctaText ?? t('View detector details');
const title = issueConfig.detector.title ?? t('Detector');

const hasWorkflowEngineUi = organization.features.includes('workflow-engine-ui');
const shouldUseMetricRuleLink = detectorType === 'metric_alert' && !hasWorkflowEngineUi;

if (shouldUseMetricRuleLink) {
return <MetricAlertSection detectorId={detectorId} />;
}

return (
<DetectorSectionContent
ctaText={detectorCtaText}
Expand All @@ -123,42 +110,6 @@ export function DetectorSection({group, project}: {group: Group; project: Projec
);
}

// This section is only shown when metric issues are enabled, but the full workflow engine UI is not.
// Remove this section once the new Monitors/Alerts UI is fully rolled out.
function MetricAlertSection({detectorId}: {detectorId: string | undefined}) {
const organization = useOrganization();
const {data: metricDetector, isLoading} = useDetectorQuery<MetricDetector>(
detectorId ?? '',
{
enabled: Boolean(detectorId),
}
);
const metricRuleId = metricDetector?.alertRuleId
? String(metricDetector.alertRuleId)
: null;
const metricRulePath = metricRuleId
? makeAlertsPathname({
path: `/rules/details/${metricRuleId}/`,
organization,
})
: undefined;

if (!defined(detectorId) || (!isLoading && !metricDetector?.alertRuleId)) {
return <LoadingError message={t('Corresponding metric alert not found')} />;
}

return (
<DetectorSectionContent
ctaText={t('View metric alert details')}
description={t(
'This issue was created by a metric alert. View the alert details to learn more.'
)}
title={t('Metric Alert')}
to={metricRulePath}
/>
);
}

function DetectorSectionContent({
ctaText,
description,
Expand Down
24 changes: 4 additions & 20 deletions static/gsApp/overrides/useMetricDetectorLimit.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {useQuery} from '@tanstack/react-query';

import {apiOptions} from 'sentry/utils/api/apiOptions';
import {useOrganization} from 'sentry/utils/useOrganization';
import {detectorListApiOptions} from 'sentry/views/detectors/hooks/index';

Expand All @@ -22,7 +21,6 @@ export function useMetricDetectorLimit(): MetricDetectorLimitResponse {
const subscription = useSubscription();
const detectorLimit = subscription?.planDetails?.metricDetectorLimit ?? UNLIMITED_QUOTA;

const isWorkflowEngine = organization.features.includes('workflow-engine-ui');
const hasFlag = organization.features.includes('workflow-engine-metric-detector-limit');

const {
Expand All @@ -34,28 +32,14 @@ export function useMetricDetectorLimit(): MetricDetectorLimitResponse {
query: 'type:metric',
limit: 1,
}),
enabled: hasFlag && isWorkflowEngine && detectorLimit !== UNLIMITED_QUOTA,
enabled: hasFlag && detectorLimit !== UNLIMITED_QUOTA,
staleTime: 5 * 1000, // Set stale time to 5 sec to avoid unnecessary re-fetching
select: data => data.headers['X-Hits'],
});

const {
data: alertRulesXHits = NO_COUNT,
isLoading: isMetricRulesLoading,
isError: isMetricRulesError,
} = useQuery({
...apiOptions.as<unknown[]>()('/organizations/$organizationIdOrSlug/alert-rules/', {
path: {organizationIdOrSlug: organization.slug},
staleTime: 5 * 1000, // Set stale time to 5 sec to avoid unnecessary re-fetching
query: {limit: 1},
}),
enabled: hasFlag && !isWorkflowEngine && detectorLimit !== UNLIMITED_QUOTA,
select: data => data.headers['X-Hits'],
});

const isLoading = isWorkflowEngine ? isDetectorsLoading : isMetricRulesLoading;
const isError = isWorkflowEngine ? isDetectorsError : isMetricRulesError;
const detectorCount = isWorkflowEngine ? detectorXHits : alertRulesXHits;
const isLoading = isDetectorsLoading;
const isError = isDetectorsError;
const detectorCount = detectorXHits;

if (!hasFlag || detectorLimit === UNLIMITED_QUOTA) {
return {
Expand Down
Loading