Skip to content

Commit e79ffcc

Browse files
committed
ref(tsc): refactor self-contained endpoints that need response headers to apiOptions
1 parent 2d5bf7d commit e79ffcc

File tree

8 files changed

+174
-173
lines changed

8 files changed

+174
-173
lines changed

static/app/utils/api/apiOptions.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ type PathParamOptions<TApiPath extends string> =
2323
? {path?: never}
2424
: {path: Record<ExtractPathParams<TApiPath>, string | number> | SkipToken};
2525

26+
const selectJson = <TData>(data: ApiResponse<TData>) => data.json;
27+
28+
export const selectJsonWithHeaders = <TData>(
29+
data: ApiResponse<TData>
30+
): ApiResponse<TData> => data;
31+
2632
function _apiOptions<
2733
TManualData = never,
2834
TApiPath extends KnownApiUrls = KnownApiUrls,
@@ -46,7 +52,7 @@ function _apiOptions<
4652
queryFn: pathParams === skipToken ? skipToken : apiFetch<TActualData>,
4753
enabled: pathParams !== skipToken,
4854
staleTime,
49-
select: data => data.json,
55+
select: selectJson,
5056
});
5157
}
5258

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
2-
import {useApiQuery} from 'sentry/utils/queryClient';
1+
import {useQuery} from '@tanstack/react-query';
2+
3+
import {apiOptions, selectJsonWithHeaders} from 'sentry/utils/api/apiOptions';
34
import {decodeList, decodeScalar} from 'sentry/utils/queryString';
45
import {hydratedSelectorData} from 'sentry/utils/replays/hydrateSelectorData';
56
import {useLocation} from 'sentry/utils/useLocation';
@@ -14,37 +15,37 @@ export function useDeadRageSelectors(params: DeadRageSelectorQueryParams) {
1415
const location = useLocation();
1516
const {query} = location;
1617

17-
const {isPending, isError, error, data, getResponseHeader} =
18-
useApiQuery<DeadRageSelectorListResponse>(
19-
[
20-
getApiUrl('/organizations/$organizationIdOrSlug/replay-selectors/', {
21-
path: {organizationIdOrSlug: organization.slug},
22-
}),
23-
{
24-
query: {
25-
query: '!count_dead_clicks:0',
26-
cursor: params.cursor,
27-
environment: decodeList(query.environment),
28-
project: query.project,
29-
statsPeriod: query.statsPeriod,
30-
start: decodeScalar(query.start),
31-
end: decodeScalar(query.end),
32-
per_page: params.per_page,
33-
sort: query[params.prefix + 'sort'] ?? params.sort,
34-
},
18+
const {isPending, isError, error, data} = useQuery({
19+
...apiOptions.as<DeadRageSelectorListResponse>()(
20+
'/organizations/$organizationIdOrSlug/replay-selectors/',
21+
{
22+
path: {organizationIdOrSlug: organization.slug},
23+
query: {
24+
query: '!count_dead_clicks:0',
25+
cursor: params.cursor,
26+
environment: decodeList(query.environment),
27+
project: query.project,
28+
statsPeriod: query.statsPeriod,
29+
start: decodeScalar(query.start),
30+
end: decodeScalar(query.end),
31+
per_page: params.per_page,
32+
sort: query[params.prefix + 'sort'] ?? params.sort,
3533
},
36-
],
37-
{staleTime: Infinity, enabled: params.enabled}
38-
);
34+
staleTime: Infinity,
35+
}
36+
),
37+
select: selectJsonWithHeaders,
38+
enabled: params.enabled,
39+
});
3940

4041
return {
4142
isLoading: isPending,
4243
isError,
4344
error,
4445
data: hydratedSelectorData(
45-
data ? data.data : [],
46+
data ? data.json.data : [],
4647
params.isWidgetData ? params.sort?.replace(/^-/, '') : null
4748
),
48-
pageLinks: getResponseHeader?.('Link') ?? undefined,
49+
pageLinks: data?.headers.Link,
4950
};
5051
}

static/app/views/alerts/list/rules/alertRulesList.tsx

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {Fragment} from 'react';
22
import styled from '@emotion/styled';
3+
import {useQuery} from '@tanstack/react-query';
34
import type {Location} from 'history';
45

56
import {Alert} from '@sentry/scraps/alert';
@@ -22,12 +23,11 @@ import {IconArrow} from 'sentry/icons';
2223
import {t} from 'sentry/locale';
2324
import type {Project} from 'sentry/types/project';
2425
import {defined} from 'sentry/utils';
25-
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
26+
import {apiOptions, selectJsonWithHeaders} from 'sentry/utils/api/apiOptions';
2627
import {uniq} from 'sentry/utils/array/uniq';
2728
import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry';
2829
import {Projects} from 'sentry/utils/projects';
29-
import type {ApiQueryKey} from 'sentry/utils/queryClient';
30-
import {setApiQueryData, useApiQuery, useQueryClient} from 'sentry/utils/queryClient';
30+
import {useQueryClient} from 'sentry/utils/queryClient';
3131
import {useRouteAnalyticsEventNames} from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames';
3232
import {useRouteAnalyticsParams} from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
3333
import {useApi} from 'sentry/utils/useApi';
@@ -45,7 +45,7 @@ import {RuleListRow} from './row';
4545
type SortField = 'date_added' | 'name' | ['incident_status', 'date_triggered'];
4646
const defaultSort: SortField = ['incident_status', 'date_triggered'];
4747

48-
function getAlertListQueryKey(orgSlug: string, query: Location['query']): ApiQueryKey {
48+
function getAlertListQueryParams(query: Location['query']) {
4949
const queryParams = {...query};
5050
queryParams.expand = ['latestIncident', 'lastTriggered'];
5151
queryParams.team = getTeamParams(queryParams.team!);
@@ -54,12 +54,18 @@ function getAlertListQueryKey(orgSlug: string, query: Location['query']): ApiQue
5454
queryParams.sort = defaultSort;
5555
}
5656

57-
return [
58-
getApiUrl('/organizations/$organizationIdOrSlug/combined-rules/', {
57+
return queryParams;
58+
}
59+
60+
function getAlertListApiOptions(orgSlug: string, query: Location['query']) {
61+
return apiOptions.as<Array<CombinedAlerts | null>>()(
62+
'/organizations/$organizationIdOrSlug/combined-rules/',
63+
{
5964
path: {organizationIdOrSlug: orgSlug},
60-
}),
61-
{query: queryParams},
62-
];
65+
query: getAlertListQueryParams(query),
66+
staleTime: 0,
67+
}
68+
);
6369
}
6470

6571
const DataConsentBanner = HookOrDefault({
@@ -82,18 +88,12 @@ export default function AlertRulesList() {
8288
});
8389

8490
// Fetch alert rules
85-
const {
86-
data: ruleListResponse = [],
87-
refetch,
88-
getResponseHeader,
89-
isPending,
90-
isError,
91-
} = useApiQuery<Array<CombinedAlerts | null>>(
92-
getAlertListQueryKey(organization.slug, location.query),
93-
{
94-
staleTime: 0,
95-
}
96-
);
91+
const alertListOptions = getAlertListApiOptions(organization.slug, location.query);
92+
const {data, refetch, isPending, isError} = useQuery({
93+
...alertListOptions,
94+
select: selectJsonWithHeaders,
95+
});
96+
const ruleListResponse = data?.json ?? [];
9797

9898
const handleChangeFilter = (activeFilters: string[]) => {
9999
const {cursor: _cursor, page: _page, ...currentQuery} = location.query;
@@ -166,11 +166,15 @@ export default function AlertRulesList() {
166166

167167
try {
168168
await api.requestPromise(deleteEndpoints[rule.type], {method: 'DELETE'});
169-
setApiQueryData<Array<CombinedAlerts | null>>(
170-
queryClient,
171-
getAlertListQueryKey(organization.slug, location.query),
172-
data => data?.filter(r => r?.id !== rule.id && r?.type !== rule.type)
173-
);
169+
queryClient.setQueryData(alertListOptions.queryKey, previous => {
170+
if (!previous) {
171+
return previous;
172+
}
173+
return {
174+
...previous,
175+
json: previous.json.filter(r => r?.id !== rule.id && r?.type !== rule.type),
176+
};
177+
});
174178
refetch();
175179
addSuccessMessage(t('Deleted rule'));
176180
} catch (_err) {
@@ -194,7 +198,7 @@ export default function AlertRulesList() {
194198
: rule.projects
195199
)
196200
);
197-
const ruleListPageLinks = getResponseHeader?.('Link');
201+
const ruleListPageLinks = data?.headers.Link;
198202

199203
const sort: {asc: boolean; field: SortField} = {
200204
asc: location.query.asc === '1',

static/app/views/alerts/rules/issue/details/issuesList.tsx

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {Fragment} from 'react';
22
import {css} from '@emotion/react';
33
import styled from '@emotion/styled';
4+
import {useQuery} from '@tanstack/react-query';
45

56
import {Flex} from '@sentry/scraps/layout';
67
import {Link} from '@sentry/scraps/link';
@@ -15,10 +16,10 @@ import {t} from 'sentry/locale';
1516
import type {IssueAlertRule} from 'sentry/types/alerts';
1617
import type {Group} from 'sentry/types/group';
1718
import type {Project} from 'sentry/types/project';
18-
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
19+
import {apiOptions, selectJsonWithHeaders} from 'sentry/utils/api/apiOptions';
1920
import {getMessage, getTitle} from 'sentry/utils/events';
2021
import type {FeedbackIssue} from 'sentry/utils/feedback/types';
21-
import {useApiQuery} from 'sentry/utils/queryClient';
22+
import {RequestError} from 'sentry/utils/requestError/requestError';
2223
import {useOrganization} from 'sentry/utils/useOrganization';
2324
import {makeFeedbackPathname} from 'sentry/views/feedback/pathnames';
2425

@@ -45,25 +46,15 @@ export function AlertRuleIssuesList({
4546
cursor,
4647
}: Props) {
4748
const organization = useOrganization();
48-
const {
49-
data: groupHistory,
50-
getResponseHeader,
51-
isPending,
52-
isError,
53-
error,
54-
} = useApiQuery<GroupHistory[]>(
55-
[
56-
getApiUrl(
57-
'/projects/$organizationIdOrSlug/$projectIdOrSlug/rules/$ruleId/group-history/',
58-
{
59-
path: {
60-
organizationIdOrSlug: organization.slug,
61-
projectIdOrSlug: project.slug,
62-
ruleId: rule.id,
63-
},
64-
}
65-
),
49+
const {data, isPending, error} = useQuery({
50+
...apiOptions.as<GroupHistory[]>()(
51+
'/projects/$organizationIdOrSlug/$projectIdOrSlug/rules/$ruleId/group-history/',
6652
{
53+
path: {
54+
organizationIdOrSlug: organization.slug,
55+
projectIdOrSlug: project.slug,
56+
ruleId: rule.id,
57+
},
6758
query: {
6859
per_page: 10,
6960
...(period && {statsPeriod: period}),
@@ -72,15 +63,17 @@ export function AlertRuleIssuesList({
7263
utc,
7364
cursor,
7465
},
75-
},
76-
],
77-
{staleTime: 0}
78-
);
66+
staleTime: 0,
67+
}
68+
),
69+
select: selectJsonWithHeaders,
70+
});
71+
const groupHistory = data?.json;
7972

80-
if (isError) {
73+
if (error instanceof RequestError) {
8174
return (
8275
<LoadingError
83-
message={(error?.responseJSON?.detail as string) ?? t('default message')}
76+
message={(error.responseJSON?.detail as string) ?? t('default message')}
8477
/>
8578
);
8679
}
@@ -140,7 +133,7 @@ export function AlertRuleIssuesList({
140133
})}
141134
</StyledPanelTable>
142135
<Flex justify="end" align="center" marginBottom="xl">
143-
<StyledPagination pageLinks={getResponseHeader?.('Link')} size="xs" />
136+
<StyledPagination pageLinks={data?.headers.Link} size="xs" />
144137
</Flex>
145138
</Fragment>
146139
);

static/app/views/discover/landing.tsx

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import styled from '@emotion/styled';
2+
import {useQuery} from '@tanstack/react-query';
23

34
import {Alert} from '@sentry/scraps/alert';
45
import {LinkButton} from '@sentry/scraps/button';
@@ -19,10 +20,9 @@ import {t, tct} from 'sentry/locale';
1920
import type {SelectValue} from 'sentry/types/core';
2021
import type {NewQuery, SavedQuery} from 'sentry/types/organization';
2122
import {trackAnalytics} from 'sentry/utils/analytics';
22-
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
23+
import {apiOptions, selectJsonWithHeaders} from 'sentry/utils/api/apiOptions';
2324
import {EventView} from 'sentry/utils/discover/eventView';
2425
import {getDiscoverLandingUrl} from 'sentry/utils/discover/urls';
25-
import {useApiQuery} from 'sentry/utils/queryClient';
2626
import {decodeScalar} from 'sentry/utils/queryString';
2727
import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
2828
import {useLocation} from 'sentry/utils/useLocation';
@@ -114,19 +114,17 @@ const useDiscoverLandingQuery = (renderPrebuilt: boolean) => {
114114
delete queryParams.cursor;
115115
}
116116

117-
return useApiQuery<SavedQuery[]>(
118-
[
119-
getApiUrl('/organizations/$organizationIdOrSlug/discover/saved/', {
120-
path: {organizationIdOrSlug: organization.slug},
121-
}),
117+
return useQuery({
118+
...apiOptions.as<SavedQuery[]>()(
119+
'/organizations/$organizationIdOrSlug/discover/saved/',
122120
{
121+
path: {organizationIdOrSlug: organization.slug},
123122
query: queryParams,
124-
},
125-
],
126-
{
127-
staleTime: 0,
128-
}
129-
);
123+
staleTime: 0,
124+
}
125+
),
126+
select: selectJsonWithHeaders,
127+
});
130128
};
131129

132130
const RENDER_PREBUILT_KEY = 'discover-render-prebuilt';
@@ -146,12 +144,12 @@ function DiscoverLanding() {
146144
const {
147145
status,
148146
error,
149-
data: savedQueries = [],
150-
getResponseHeader,
147+
data: savedQueriesResponse,
151148
refetch: refreshSavedQueries,
152149
} = useDiscoverLandingQuery(renderPrebuilt);
153150

154-
const savedQueriesPageLinks = getResponseHeader?.('Link');
151+
const savedQueries = savedQueriesResponse?.json ?? [];
152+
const savedQueriesPageLinks = savedQueriesResponse?.headers.Link;
155153

156154
const to = makeDiscoverPathname({
157155
path: `/homepage/`,

0 commit comments

Comments
 (0)