Skip to content

Commit 59f7846

Browse files
committed
fix: uptime typings
1 parent 48f0bcf commit 59f7846

File tree

4 files changed

+158
-29
lines changed

4 files changed

+158
-29
lines changed

static/app/views/automations/components/connectedMonitorsList.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,7 @@ export function ConnectedMonitorsList({
8686
const organization = useOrganization();
8787
const canEdit = Boolean(connectedDetectorIds && typeof toggleConnected === 'function');
8888

89-
const {
90-
data: detectorsResponse,
91-
isLoading,
92-
isError,
93-
isSuccess,
94-
} = useQuery({
89+
const {data, isLoading, isError, isSuccess} = useQuery({
9590
...detectorListApiOptions(organization, {
9691
ids: detectorIds ?? undefined,
9792
limit: limit ?? undefined,
@@ -103,9 +98,9 @@ export function ConnectedMonitorsList({
10398
enabled: detectorIds === null || detectorIds.length > 0,
10499
});
105100

106-
const detectors = detectorsResponse?.json;
107-
const pageLinks = detectorsResponse?.headers.Link;
108-
const totalCountInt = detectorsResponse?.headers['X-Hits'] ?? 0;
101+
const detectors = data?.json;
102+
const pageLinks = data?.headers.Link;
103+
const totalCountInt = data?.headers['X-Hits'] ?? 0;
109104

110105
const paginationCaption = useMemo(() => {
111106
if (isLoading || !detectors || detectors?.length === 0 || limit === null) {
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import {expectTypeOf} from 'expect-type';
2+
import {OrganizationFixture} from 'sentry-fixture/organization';
3+
4+
import type {Detector, UptimeDetector} from 'sentry/types/workflowEngine/detectors';
5+
import type {ApiResponse} from 'sentry/utils/api/apiFetch';
6+
import {parseQueryKey} from 'sentry/utils/api/apiQueryKey';
7+
import {detectorListApiOptions} from 'sentry/views/detectors/hooks';
8+
9+
const organization = OrganizationFixture();
10+
11+
describe('detectorListApiOptions', () => {
12+
describe('query construction', () => {
13+
it('excludes issue_stream detectors by default', () => {
14+
const {options} = parseQueryKey(detectorListApiOptions(organization).queryKey);
15+
expect(options?.query?.query).toBe('!type:issue_stream');
16+
});
17+
18+
it('does not exclude issue_stream when includeIssueStreamDetectors is true', () => {
19+
const {options} = parseQueryKey(
20+
detectorListApiOptions(organization, {
21+
includeIssueStreamDetectors: true,
22+
}).queryKey
23+
);
24+
expect(options?.query?.query).toBeUndefined();
25+
});
26+
27+
it('adds type filter when type is provided', () => {
28+
const {options} = parseQueryKey(
29+
detectorListApiOptions(organization, {type: 'uptime'}).queryKey
30+
);
31+
expect(options?.query?.query).toBe('!type:issue_stream type:uptime');
32+
});
33+
34+
it('combines type filter with custom query', () => {
35+
const {options} = parseQueryKey(
36+
detectorListApiOptions(organization, {
37+
type: 'uptime',
38+
query: 'my-search',
39+
}).queryKey
40+
);
41+
expect(options?.query?.query).toBe('!type:issue_stream type:uptime my-search');
42+
});
43+
44+
it('combines includeIssueStreamDetectors with type', () => {
45+
const {options} = parseQueryKey(
46+
detectorListApiOptions(organization, {
47+
type: 'uptime',
48+
includeIssueStreamDetectors: true,
49+
}).queryKey
50+
);
51+
expect(options?.query?.query).toBe('type:uptime');
52+
});
53+
54+
it('passes through pagination and filter params', () => {
55+
const {options} = parseQueryKey(
56+
detectorListApiOptions(organization, {
57+
cursor: 'abc123',
58+
limit: 25,
59+
sortBy: 'name',
60+
projects: [1, 2],
61+
ids: ['10', '20'],
62+
}).queryKey
63+
);
64+
expect(options?.query).toEqual(
65+
expect.objectContaining({
66+
cursor: 'abc123',
67+
per_page: 25,
68+
sortBy: 'name',
69+
project: [1, 2],
70+
id: ['10', '20'],
71+
})
72+
);
73+
});
74+
75+
it('builds the correct URL', () => {
76+
const {url} = parseQueryKey(detectorListApiOptions(organization).queryKey);
77+
expect(url).toBe(`/organizations/${organization.slug}/detectors/`);
78+
});
79+
});
80+
81+
describe('types', () => {
82+
it('returns Detector[] by default', () => {
83+
const options = detectorListApiOptions(organization);
84+
expectTypeOf(options.select).returns.toEqualTypeOf<Detector[]>();
85+
});
86+
87+
it('returns UptimeDetector[] when type is uptime', () => {
88+
const options = detectorListApiOptions(organization, {type: 'uptime'});
89+
expectTypeOf(options.select).returns.toEqualTypeOf<UptimeDetector[]>();
90+
});
91+
92+
it('returns Detector[] when no type is provided with other params', () => {
93+
const options = detectorListApiOptions(organization, {cursor: 'abc'});
94+
expectTypeOf(options.select).returns.toEqualTypeOf<Detector[]>();
95+
});
96+
97+
it('returns ApiResponse<UptimeDetector[]> when selectJsonWithHeaders is used', () => {
98+
const options = detectorListApiOptions(organization, {type: 'uptime'});
99+
// The select override changes the type, but the queryFn data type is correct
100+
expectTypeOf(options.select)
101+
.parameter(0)
102+
.toEqualTypeOf<ApiResponse<UptimeDetector[]>>();
103+
});
104+
});
105+
});

static/app/views/detectors/hooks/index.ts

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {Organization} from 'sentry/types/organization';
66
import {
77
type BaseDetectorUpdatePayload,
88
type Detector,
9+
type UptimeDetector,
910
} from 'sentry/types/workflowEngine/detectors';
1011
import {apiOptions} from 'sentry/utils/api/apiOptions';
1112
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
@@ -14,7 +15,16 @@ import {useApiQuery, useMutation, useQueryClient} from 'sentry/utils/queryClient
1415
import {useApi} from 'sentry/utils/useApi';
1516
import {useOrganization} from 'sentry/utils/useOrganization';
1617

17-
interface UseDetectorsApiOptionsParams {
18+
interface DetectorTypeMap {
19+
uptime: UptimeDetector;
20+
}
21+
22+
type DetectorByType<T extends keyof DetectorTypeMap | undefined> =
23+
T extends keyof DetectorTypeMap ? DetectorTypeMap[T] : Detector;
24+
25+
interface UseDetectorsApiOptionsParams<
26+
TType extends keyof DetectorTypeMap | undefined = undefined,
27+
> {
1828
cursor?: string;
1929
ids?: string[];
2030
/**
@@ -27,19 +37,33 @@ interface UseDetectorsApiOptionsParams {
2737
projects?: number[];
2838
query?: string;
2939
sortBy?: string;
40+
/**
41+
* When set, the query automatically includes `type:{value}` and
42+
* the return type narrows to the matching detector subtype.
43+
*/
44+
type?: TType;
3045
}
3146

3247
const createDetectorQuery = (
3348
query: string | undefined,
34-
options: {includeIssueStreamDetectors: boolean}
49+
options: {includeIssueStreamDetectors: boolean; type?: string}
3550
) => {
36-
if (options.includeIssueStreamDetectors) {
37-
return query;
51+
const parts: string[] = [];
52+
if (!options.includeIssueStreamDetectors) {
53+
parts.push('!type:issue_stream');
54+
}
55+
if (options.type) {
56+
parts.push(`type:${options.type}`);
57+
}
58+
if (query) {
59+
parts.push(query);
3860
}
39-
return `!type:issue_stream ${query ?? ''}`.trim();
61+
return parts.join(' ') || undefined;
4062
};
4163

42-
export function detectorListApiOptions(
64+
export function detectorListApiOptions<
65+
TType extends keyof DetectorTypeMap | undefined = undefined,
66+
>(
4367
organization: Organization,
4468
{
4569
query,
@@ -48,22 +72,26 @@ export function detectorListApiOptions(
4872
limit,
4973
cursor,
5074
ids,
75+
type,
5176
includeIssueStreamDetectors = false,
52-
}: UseDetectorsApiOptionsParams = {}
77+
}: UseDetectorsApiOptionsParams<TType> = {} as UseDetectorsApiOptionsParams<TType>
5378
) {
5479
return queryOptions({
55-
...apiOptions.as<Detector[]>()('/organizations/$organizationIdOrSlug/detectors/', {
56-
path: {organizationIdOrSlug: organization.slug},
57-
query: {
58-
query: createDetectorQuery(query, {includeIssueStreamDetectors}),
59-
sortBy,
60-
project: projects,
61-
per_page: limit,
62-
cursor,
63-
id: ids,
64-
},
65-
staleTime: 0,
66-
}),
80+
...apiOptions.as<Array<DetectorByType<TType>>>()(
81+
'/organizations/$organizationIdOrSlug/detectors/',
82+
{
83+
path: {organizationIdOrSlug: organization.slug},
84+
query: {
85+
query: createDetectorQuery(query, {includeIssueStreamDetectors, type}),
86+
sortBy,
87+
project: projects,
88+
per_page: limit,
89+
cursor,
90+
id: ids,
91+
},
92+
staleTime: 0,
93+
}
94+
),
6795
retry: false,
6896
});
6997
}

static/app/views/insights/uptime/views/overview.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ export default function UptimeOverview() {
4848

4949
const {data, isPending} = useQuery({
5050
...detectorListApiOptions(organization, {
51-
query: `type:uptime ${location.query.query ?? ''}`,
51+
type: 'uptime',
52+
query: decodeScalar(location.query.query),
5253
cursor: decodeScalar(location.query.cursor),
5354
projects: project.map(Number),
5455
}),

0 commit comments

Comments
 (0)