Skip to content

Commit 71191ad

Browse files
authored
ref(tsc): code-mappings endpoint to apiOptions (#112915)
1 parent 2a34313 commit 71191ad

File tree

3 files changed

+104
-108
lines changed

3 files changed

+104
-108
lines changed

static/app/views/settings/organizationIntegrations/integrationCodeMappings.tsx

Lines changed: 73 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {Fragment, useCallback, useMemo} from 'react';
22
import styled from '@emotion/styled';
3+
import {useQuery, useQueryClient} from '@tanstack/react-query';
34
import sortBy from 'lodash/sortBy';
45

56
import {Button, LinkButton} from '@sentry/scraps/button';
@@ -20,15 +21,9 @@ import {t, tct} from 'sentry/locale';
2021
import type {Integration, RepositoryProjectPathConfig} from 'sentry/types/integrations';
2122
import {trackAnalytics} from 'sentry/utils/analytics';
2223
import {useFetchAllPages} from 'sentry/utils/api/apiFetch';
23-
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
24+
import {apiOptions, selectJsonWithHeaders} from 'sentry/utils/api/apiOptions';
2425
import {getIntegrationIcon} from 'sentry/utils/integrationUtil';
25-
import {
26-
useApiQuery,
27-
useInfiniteQuery,
28-
useMutation,
29-
useQueryClient,
30-
type ApiQueryKey,
31-
} from 'sentry/utils/queryClient';
26+
import {useInfiniteQuery, useMutation} from 'sentry/utils/queryClient';
3227
import {organizationRepositoriesInfiniteOptions} from 'sentry/utils/repositories/repoQueryOptions';
3328
import type {RequestError} from 'sentry/utils/requestError/requestError';
3429
import {useRouteAnalyticsEventNames} from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames';
@@ -65,28 +60,33 @@ function getDocsLink(integration: Integration): string {
6560
return `https://docs.sentry.io/product/integrations/source-code-mgmt/${docsKey}/#stack-trace-linking`;
6661
}
6762

68-
function makePathConfigQueryKey({
63+
function codeMappingsApiOptions({
6964
orgSlug,
7065
integrationId,
7166
cursor,
7267
}: {
73-
integrationId: string;
7468
orgSlug: string;
7569
cursor?: string | string[] | null;
76-
}): ApiQueryKey {
77-
return [
78-
getApiUrl('/organizations/$organizationIdOrSlug/code-mappings/', {
70+
integrationId?: string;
71+
}) {
72+
return apiOptions.as<RepositoryProjectPathConfig[]>()(
73+
'/organizations/$organizationIdOrSlug/code-mappings/',
74+
{
7975
path: {organizationIdOrSlug: orgSlug},
80-
}),
81-
{query: {integrationId, cursor}},
82-
];
76+
query: {integrationId, cursor},
77+
staleTime: 10_000,
78+
}
79+
);
8380
}
8481

85-
function useDeletePathConfig() {
82+
function useDeletePathConfig({
83+
queryKey,
84+
}: {
85+
queryKey: ReturnType<typeof codeMappingsApiOptions>['queryKey'];
86+
}) {
8687
const api = useApi({persistInFlight: false});
8788
const organization = useOrganization();
8889
const queryClient = useQueryClient();
89-
const location = useLocation();
9090
return useMutation<
9191
RepositoryProjectPathConfig,
9292
RequestError,
@@ -102,15 +102,13 @@ function useDeletePathConfig() {
102102
},
103103
onMutate: pathConfig => {
104104
if (pathConfig.integrationId) {
105-
queryClient.setQueryData<RepositoryProjectPathConfig[]>(
106-
makePathConfigQueryKey({
107-
orgSlug: organization.slug,
108-
integrationId: pathConfig.integrationId,
109-
cursor: location.query.cursor,
110-
}),
111-
(data: RepositoryProjectPathConfig[] = []) => {
112-
return data.filter(config => config.id !== pathConfig.id);
113-
}
105+
queryClient.setQueryData(queryKey, prevData =>
106+
prevData
107+
? {
108+
...prevData,
109+
json: prevData.json.filter(config => config.id !== pathConfig.id),
110+
}
111+
: prevData
114112
);
115113
}
116114
},
@@ -122,7 +120,9 @@ function useDeletePathConfig() {
122120
},
123121
onSettled: () => {
124122
queryClient.invalidateQueries({
125-
queryKey: [`/organizations/${organization.slug}/code-mappings/`],
123+
queryKey: codeMappingsApiOptions({
124+
orgSlug: organization.slug,
125+
}).queryKey,
126126
});
127127
},
128128
});
@@ -144,19 +144,20 @@ export function IntegrationCodeMappings({integration}: {integration: Integration
144144
const location = useLocation();
145145
const integrationId = integration.id;
146146

147+
const pathConfigsQueryOptions = codeMappingsApiOptions({
148+
orgSlug: organization.slug,
149+
integrationId,
150+
cursor: location.query.cursor,
151+
});
152+
147153
const {
148-
data: fetchedPathConfigs = [],
154+
data: pathConfigsResponse,
149155
isPending: isPendingPathConfigs,
150156
isError: isErrorPathConfigs,
151-
getResponseHeader: getPathConfigsResponseHeader,
152-
} = useApiQuery<RepositoryProjectPathConfig[]>(
153-
makePathConfigQueryKey({
154-
orgSlug: organization.slug,
155-
integrationId,
156-
cursor: location.query.cursor,
157-
}),
158-
{staleTime: 10_000}
159-
);
157+
} = useQuery({
158+
...pathConfigsQueryOptions,
159+
select: selectJsonWithHeaders,
160+
});
160161

161162
const repositoriesQuery = useInfiniteQuery({
162163
...organizationRepositoriesInfiniteOptions({
@@ -182,11 +183,11 @@ export function IntegrationCodeMappings({integration}: {integration: Integration
182183
(!!hasNextReposPage && !isErrorRepos);
183184

184185
const pathConfigs = useMemo(() => {
185-
return sortBy(fetchedPathConfigs, [
186+
return sortBy(pathConfigsResponse?.json ?? [], [
186187
({projectSlug}) => projectSlug,
187188
({id}) => parseInt(id, 10),
188189
]);
189-
}, [fetchedPathConfigs]);
190+
}, [pathConfigsResponse?.json]);
190191

191192
const repos = useMemo(
192193
() => fetchedRepos.filter(repo => repo.integrationId === integrationId),
@@ -200,43 +201,40 @@ export function IntegrationCodeMappings({integration}: {integration: Integration
200201
[projects]
201202
);
202203

203-
const {mutate: deletePathConfig} = useDeletePathConfig();
204+
const {mutate: deletePathConfig} = useDeletePathConfig({
205+
queryKey: pathConfigsQueryOptions.queryKey,
206+
});
204207

205-
const invalidateCodeMappings = useCallback(() => {
206-
queryClient.invalidateQueries({
207-
queryKey: [`/organizations/${organization.slug}/code-mappings/`],
208+
const openCodeMappingModal = (pathConfig?: RepositoryProjectPathConfig) => {
209+
trackAnalytics('integrations.stacktrace_start_setup', {
210+
setup_type: 'manual',
211+
view: 'integration_configuration_detail',
212+
provider: integration.provider.key,
213+
organization,
208214
});
209-
}, [queryClient, organization.slug]);
210215

211-
const openCodeMappingModal = useCallback(
212-
(pathConfig?: RepositoryProjectPathConfig) => {
213-
trackAnalytics('integrations.stacktrace_start_setup', {
214-
setup_type: 'manual',
215-
view: 'integration_configuration_detail',
216-
provider: integration.provider.key,
217-
organization,
218-
});
219-
220-
openModal(
221-
modalProps => (
222-
<RepositoryProjectPathConfigModal
223-
{...modalProps}
224-
organization={organization}
225-
integration={integration}
226-
projects={projects}
227-
repos={repos}
228-
existingConfig={pathConfig}
229-
/>
230-
),
231-
{
232-
onClose: () => {
233-
invalidateCodeMappings();
234-
},
235-
}
236-
);
237-
},
238-
[repos, projects, integration, organization, invalidateCodeMappings]
239-
);
216+
openModal(
217+
modalProps => (
218+
<RepositoryProjectPathConfigModal
219+
{...modalProps}
220+
organization={organization}
221+
integration={integration}
222+
projects={projects}
223+
repos={repos}
224+
existingConfig={pathConfig}
225+
/>
226+
),
227+
{
228+
onClose: () => {
229+
queryClient.invalidateQueries({
230+
queryKey: codeMappingsApiOptions({
231+
orgSlug: organization.slug,
232+
}).queryKey,
233+
});
234+
},
235+
}
236+
);
237+
};
240238

241239
const isLoading = isPendingPathConfigs || isPendingRepos;
242240

@@ -252,7 +250,7 @@ export function IntegrationCodeMappings({integration}: {integration: Integration
252250
return <LoadingError message={t('Error loading repositories')} />;
253251
}
254252

255-
const pathConfigsPageLinks = getPathConfigsResponseHeader?.('Link');
253+
const pathConfigsPageLinks = pathConfigsResponse?.headers.Link;
256254
const docsLink = getDocsLink(integration);
257255

258256
return (

static/app/views/settings/project/projectOwnership/addCodeOwnerModal.tsx

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {Fragment, useState, type Dispatch, type SetStateAction} from 'react';
22
import styled from '@emotion/styled';
3+
import {skipToken, useQuery} from '@tanstack/react-query';
34

45
import {Alert} from '@sentry/scraps/alert';
56
import {Button, LinkButton} from '@sentry/scraps/button';
@@ -24,6 +25,7 @@ import type {
2425
} from 'sentry/types/integrations';
2526
import type {Organization} from 'sentry/types/organization';
2627
import type {Project} from 'sentry/types/project';
28+
import {apiOptions} from 'sentry/utils/api/apiOptions';
2729
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
2830
import {getIntegrationIcon} from 'sentry/utils/integrationUtil';
2931
import {
@@ -59,14 +61,15 @@ export function AddCodeOwnerModal({
5961
data: codeMappings,
6062
isPending: isCodeMappingsPending,
6163
isError: isCodeMappingsError,
62-
} = useApiQuery<RepositoryProjectPathConfig[]>(
63-
[
64-
getApiUrl('/organizations/$organizationIdOrSlug/code-mappings/', {
64+
} = useQuery(
65+
apiOptions.as<RepositoryProjectPathConfig[]>()(
66+
'/organizations/$organizationIdOrSlug/code-mappings/',
67+
{
6568
path: {organizationIdOrSlug: organization.slug},
66-
}),
67-
{query: {project: project.id}},
68-
],
69-
{staleTime: Infinity}
69+
query: {project: project.id},
70+
staleTime: Infinity,
71+
}
72+
)
7073
);
7174

7275
const {
@@ -85,19 +88,16 @@ export function AddCodeOwnerModal({
8588

8689
const [codeMappingId, setCodeMappingId] = useState<string | null>(null);
8790

88-
const {data: codeownersFile} = useApiQuery<CodeownersFile>(
89-
[
90-
getApiUrl(
91-
'/organizations/$organizationIdOrSlug/code-mappings/$configId/codeowners/',
92-
{
93-
path: {
94-
organizationIdOrSlug: organization.slug,
95-
configId: codeMappingId!,
96-
},
97-
}
98-
),
99-
],
100-
{staleTime: Infinity, enabled: Boolean(codeMappingId)}
91+
const {data: codeownersFile} = useQuery(
92+
apiOptions.as<CodeownersFile>()(
93+
'/organizations/$organizationIdOrSlug/code-mappings/$configId/codeowners/',
94+
{
95+
path: codeMappingId
96+
? {organizationIdOrSlug: organization.slug, configId: codeMappingId}
97+
: skipToken,
98+
staleTime: Infinity,
99+
}
100+
)
101101
);
102102

103103
const mutation = useMutation<

static/gsApp/views/seerAutomation/onboarding/hooks/useCodeMappings.tsx

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import {useEffect, useMemo} from 'react';
22
import * as Sentry from '@sentry/react';
3+
import {skipToken, useQuery} from '@tanstack/react-query';
34

45
import type {RepositoryProjectPathConfig} from 'sentry/types/integrations';
5-
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
6-
import {useApiQuery} from 'sentry/utils/queryClient';
6+
import {apiOptions} from 'sentry/utils/api/apiOptions';
77
import {useOrganization} from 'sentry/utils/useOrganization';
88

99
interface UseCodeMappingsParams {
@@ -19,17 +19,15 @@ export function useCodeMappings({enabled}: UseCodeMappingsParams) {
1919
isLoading,
2020
isPending,
2121
isError,
22-
} = useApiQuery<RepositoryProjectPathConfig[]>(
23-
[
24-
getApiUrl('/organizations/$organizationIdOrSlug/code-mappings/', {
25-
path: {organizationIdOrSlug: organization.slug},
26-
}),
27-
],
28-
{
29-
// Code mappings are not updated frequently, so we can cache them for a longer time.
30-
staleTime: FIFTEEN_MINUTES,
31-
enabled,
32-
}
22+
} = useQuery(
23+
apiOptions.as<RepositoryProjectPathConfig[]>()(
24+
'/organizations/$organizationIdOrSlug/code-mappings/',
25+
{
26+
path: enabled ? {organizationIdOrSlug: organization.slug} : skipToken,
27+
// Code mappings are not updated frequently, so we can cache them for a longer time.
28+
staleTime: FIFTEEN_MINUTES,
29+
}
30+
)
3331
);
3432

3533
// Create a map of repository ID to project slugs based on code mappings.

0 commit comments

Comments
 (0)