diff --git a/static/app/views/detectors/components/detectorTypeForm.tsx b/static/app/views/detectors/components/detectorTypeForm.tsx
index 091094425899ef..892775f477bd5e 100644
--- a/static/app/views/detectors/components/detectorTypeForm.tsx
+++ b/static/app/views/detectors/components/detectorTypeForm.tsx
@@ -1,5 +1,6 @@
import {useTheme} from '@emotion/react';
import styled from '@emotion/styled';
+import {parseAsStringEnum, useQueryState} from 'nuqs';
import {Flex, Stack} from 'sentry/components/core/layout';
import {ExternalLink, Link} from 'sentry/components/core/link';
@@ -9,8 +10,6 @@ import Hook from 'sentry/components/hook';
import {t, tct} from 'sentry/locale';
import HookStore from 'sentry/stores/hookStore';
import type {DetectorType} from 'sentry/types/workflowEngine/detectors';
-import {useLocation} from 'sentry/utils/useLocation';
-import {useNavigate} from 'sentry/utils/useNavigate';
import useOrganization from 'sentry/utils/useOrganization';
import {
makeAutomationBasePathname,
@@ -44,9 +43,28 @@ export function DetectorTypeForm() {
);
}
+type SelectableDetectorType = Extract<
+ DetectorType,
+ 'metric_issue' | 'monitor_check_in_failure' | 'uptime_domain_failure'
+>;
+
+const ALLOWED_DETECTOR_TYPES = [
+ 'metric_issue',
+ 'monitor_check_in_failure',
+ 'uptime_domain_failure',
+] as const satisfies SelectableDetectorType[];
+
+const detectorTypeParser = parseAsStringEnum(ALLOWED_DETECTOR_TYPES)
+ .withOptions({history: 'replace', clearOnDefault: false})
+ .withDefault(ALLOWED_DETECTOR_TYPES[0]);
+
+export function useDetectorTypeQueryState() {
+ return useQueryState('detectorType', detectorTypeParser);
+}
+
interface DetectorTypeOption {
description: string;
- id: DetectorType;
+ id: SelectableDetectorType;
name: string;
visualization: React.ReactNode;
disabled?: boolean;
@@ -54,23 +72,15 @@ interface DetectorTypeOption {
}
function MonitorTypeField() {
- const location = useLocation();
- const navigate = useNavigate();
- const selectedDetectorType = location.query.detectorType as DetectorType;
+ const [selectedDetectorType, setDetectorType] = useDetectorTypeQueryState();
const useMetricDetectorLimit =
HookStore.get('react-hook:use-metric-detector-limit')[0] ?? (() => null);
const quota = useMetricDetectorLimit();
const canCreateMetricDetector = !quota?.hasReachedLimit;
- const handleChange = (value: DetectorType) => {
- navigate({
- pathname: location.pathname,
- query: {
- ...location.query,
- detectorType: value,
- },
- });
+ const handleChange = (value: SelectableDetectorType) => {
+ setDetectorType(value);
};
const options: DetectorTypeOption[] = [
diff --git a/static/app/views/detectors/new-settings.tsx b/static/app/views/detectors/new-settings.tsx
index fe702a34999ad5..85c580f8b4f958 100644
--- a/static/app/views/detectors/new-settings.tsx
+++ b/static/app/views/detectors/new-settings.tsx
@@ -1,12 +1,13 @@
+import {parseAsString, useQueryState} from 'nuqs';
+
import * as Layout from 'sentry/components/layouts/thirds';
import LoadingError from 'sentry/components/loadingError';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
import {useWorkflowEngineFeatureGate} from 'sentry/components/workflowEngine/useWorkflowEngineFeatureGate';
import {t} from 'sentry/locale';
-import type {DetectorType} from 'sentry/types/workflowEngine/detectors';
-import {useLocation} from 'sentry/utils/useLocation';
import useProjects from 'sentry/utils/useProjects';
+import {useDetectorTypeQueryState} from 'sentry/views/detectors/components/detectorTypeForm';
import {NewDetectorForm} from 'sentry/views/detectors/components/forms';
import {DetectorFormProvider} from 'sentry/views/detectors/components/forms/context';
import {
@@ -15,13 +16,13 @@ import {
} from 'sentry/views/detectors/utils/detectorTypeConfig';
export default function DetectorNewSettings() {
- const location = useLocation();
const {projects, fetching: isFetchingProjects} = useProjects();
- const detectorType = location.query.detectorType as DetectorType;
+ const [detectorType] = useDetectorTypeQueryState();
+ const [projectId] = useQueryState('project', parseAsString);
useWorkflowEngineFeatureGate({redirect: true});
- if (!isValidDetectorType(detectorType)) {
- return ;
+ if (!detectorType || !isValidDetectorType(detectorType)) {
+ return ;
}
if (isFetchingProjects) {
@@ -36,7 +37,7 @@ export default function DetectorNewSettings() {
);
}
- const project = projects.find(p => p.id === (location.query.project as string));
+ const project = projectId ? projects.find(p => p.id === projectId) : undefined;
if (!project) {
return ;
}
diff --git a/static/app/views/detectors/new.spec.tsx b/static/app/views/detectors/new.spec.tsx
index ba70e8de97d429..fc1e98d364c3fa 100644
--- a/static/app/views/detectors/new.spec.tsx
+++ b/static/app/views/detectors/new.spec.tsx
@@ -23,9 +23,6 @@ describe('DetectorNew', () => {
it('sets query parameters for project, environment, and detectorType', async () => {
const {router} = render();
- // Next button should be disabled if no detectorType is selected
- expect(screen.getByRole('button', {name: 'Next'})).toBeDisabled();
-
// Set detectorType
await userEvent.click(screen.getByRole('radio', {name: 'Uptime'}));
diff --git a/static/app/views/detectors/new.tsx b/static/app/views/detectors/new.tsx
index 852d9de475e83b..606308aba71eef 100644
--- a/static/app/views/detectors/new.tsx
+++ b/static/app/views/detectors/new.tsx
@@ -1,19 +1,21 @@
import {useTheme} from '@emotion/react';
+import {parseAsString, useQueryState} from 'nuqs';
import {Breadcrumbs} from 'sentry/components/breadcrumbs';
import {Button} from 'sentry/components/core/button';
import {LinkButton} from 'sentry/components/core/button/linkButton';
-import {Tooltip} from 'sentry/components/core/tooltip';
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
import EditLayout from 'sentry/components/workflowEngine/layout/edit';
import {useWorkflowEngineFeatureGate} from 'sentry/components/workflowEngine/useWorkflowEngineFeatureGate';
import {t} from 'sentry/locale';
import type {DetectorType} from 'sentry/types/workflowEngine/detectors';
-import {useLocation} from 'sentry/utils/useLocation';
import {useNavigate} from 'sentry/utils/useNavigate';
import useOrganization from 'sentry/utils/useOrganization';
import useProjects from 'sentry/utils/useProjects';
-import {DetectorTypeForm} from 'sentry/views/detectors/components/detectorTypeForm';
+import {
+ DetectorTypeForm,
+ useDetectorTypeQueryState,
+} from 'sentry/views/detectors/components/detectorTypeForm';
import {MonitorFeedbackButton} from 'sentry/views/detectors/components/monitorFeedbackButton';
import {makeMonitorBasePathname} from 'sentry/views/detectors/pathnames';
@@ -43,14 +45,11 @@ export default function DetectorNew() {
const navigate = useNavigate();
const organization = useOrganization();
useWorkflowEngineFeatureGate({redirect: true});
- const location = useLocation();
const {projects} = useProjects();
const theme = useTheme();
const maxWidth = theme.breakpoints.xl;
- const detectorType = location.query.detectorType as DetectorType;
-
- const projectIdFromLocation =
- typeof location.query.project === 'string' ? location.query.project : undefined;
+ const [detectorType] = useDetectorTypeQueryState();
+ const [projectIdFromLocation] = useQueryState('project', parseAsString);
const defaultProject = projects.find(p => p.isMember) ?? projects[0];
const newMonitorName = t('New Monitor');
@@ -62,7 +61,7 @@ export default function DetectorNew() {
navigate({
pathname: `${makeMonitorBasePathname(organization.slug)}new/settings/`,
query: {
- detectorType: location.query.detectorType as DetectorType,
+ detectorType,
project: data.project,
},
});
@@ -95,15 +94,9 @@ export default function DetectorNew() {
{t('Cancel')}
-
-
-
+
);