Skip to content
Merged
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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {getRepositoryWithSettingsQueryKey} from 'sentry/components/repositories/useRepositoryWithSettings';
import type {RepositoryWithSettings} from 'sentry/types/integrations';
import type {Repository, RepositoryWithSettings} from 'sentry/types/integrations';
import {apiOptions} from 'sentry/utils/api/apiOptions';
import {
fetchMutation,
useMutation,
Expand Down Expand Up @@ -45,7 +46,22 @@ export function useBulkUpdateRepositorySettings(
...options,
onSettled: (data, error, variables, onMutateResult, context) => {
queryClient.invalidateQueries({
queryKey: [`/organizations/${organization.slug}/repos/`],
queryKey: apiOptions.as<Repository[]>()(
'/organizations/$organizationIdOrSlug/repos/',
{
path: {organizationIdOrSlug: organization.slug},
staleTime: 0,
}
).queryKey,
Comment thread
cursor[bot] marked this conversation as resolved.
});
queryClient.invalidateQueries({
queryKey: apiOptions.asInfinite<Repository[]>()(
'/organizations/$organizationIdOrSlug/repos/',
{
path: {organizationIdOrSlug: organization.slug},
staleTime: 0,
}
).queryKey,
});
(data ?? []).forEach(repo => {
const queryKey = getRepositoryWithSettingsQueryKey(organization, repo.id);
Expand Down
19 changes: 19 additions & 0 deletions static/app/utils/repositories/repoQueryOptions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import type {InfiniteData} from '@tanstack/react-query';

import type {Repository, RepositoryWithSettings} from 'sentry/types/integrations';
import type {Organization} from 'sentry/types/organization';
import type {ApiResponse} from 'sentry/utils/api/apiFetch';
import {apiOptions} from 'sentry/utils/api/apiOptions';
import {encodeSort} from 'sentry/utils/discover/eventView';
import type {Sort} from 'sentry/utils/discover/fields';

/**
* Select helper that flattens infinite pages and deduplicates repositories
* by `externalId`. Use as the `select` option with `useInfiniteQuery`.
*/
export function selectUniqueRepos(data: InfiniteData<ApiResponse<Repository[]>>) {
const uniqueReposMap = new Map<string, Repository>();
for (const page of data.pages) {
for (const repo of page.json) {
if (repo.externalId && !uniqueReposMap.has(repo.externalId)) {
uniqueReposMap.set(repo.externalId, repo);
}
}
}
return Array.from(uniqueReposMap.values());
}

export function organizationRepositoriesInfiniteOptions({
organization,
query,
Expand Down
22 changes: 9 additions & 13 deletions static/app/utils/useRepositories.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import type {Repository} from 'sentry/types/integrations';
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
import {useApiQuery, type ApiQueryKey} from 'sentry/utils/queryClient';
import {useQuery} from '@tanstack/react-query';

function getRepositoriesQueryKey({orgSlug}: {orgSlug: string}): ApiQueryKey {
return [
getApiUrl('/organizations/$organizationIdOrSlug/repos/', {
path: {organizationIdOrSlug: orgSlug},
}),
];
}
import type {Repository} from 'sentry/types/integrations';
import {apiOptions} from 'sentry/utils/api/apiOptions';

export function useRepositories({orgSlug}: {orgSlug: string}) {
return useApiQuery<Repository[]>(getRepositoriesQueryKey({orgSlug}), {
staleTime: Infinity,
});
return useQuery(
apiOptions.as<Repository[]>()('/organizations/$organizationIdOrSlug/repos/', {
path: {organizationIdOrSlug: orgSlug},
staleTime: Infinity,
})
);
}
23 changes: 9 additions & 14 deletions static/app/views/onboarding/components/useScmRepoSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import type {
Repository,
} from 'sentry/types/integrations';
import {RepositoryStatus} from 'sentry/types/integrations';
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
import type {ApiQueryKey} from 'sentry/utils/queryClient';
import {fetchDataQuery, fetchMutation, useQueryClient} from 'sentry/utils/queryClient';
import {apiOptions} from 'sentry/utils/api/apiOptions';
import {fetchMutation, useQueryClient} from 'sentry/utils/queryClient';
import {useOrganization} from 'sentry/utils/useOrganization';

interface UseScmRepoSelectionOptions {
Expand Down Expand Up @@ -64,23 +63,19 @@ export function useScmRepoSelection({
// pagination issues with the full list.
setBusy(true);
try {
const queryKey: ApiQueryKey = [
getApiUrl('/organizations/$organizationIdOrSlug/repos/', {
path: {organizationIdOrSlug: organization.slug},
}),
const reposQueryOptions = apiOptions.as<Repository[]>()(
'/organizations/$organizationIdOrSlug/repos/',
{
path: {organizationIdOrSlug: organization.slug},
query: {
status: 'active',
integration_id: integration.id,
query: repo.identifier,
},
},
];
const [matches] = await queryClient.fetchQuery({
queryKey,
queryFn: fetchDataQuery<Repository[]>,
staleTime: 0,
});
staleTime: 0,
}
);
const matches = (await queryClient.fetchQuery(reposQueryOptions)).json;
// The query param above is an icontains filter to narrow results
// and avoid pagination. The exact match here uses Repository.name
// against IntegrationRepository.identifier — the same comparison the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Fragment, useState} from 'react';
import {useQuery} from '@tanstack/react-query';

import {Alert} from '@sentry/scraps/alert';
import {LinkButton} from '@sentry/scraps/button';
Expand All @@ -15,8 +16,7 @@ import {IconCommit} from 'sentry/icons';
import {t} from 'sentry/locale';
import {RepositoryStore} from 'sentry/stores/repositoryStore';
import type {Integration, Repository} from 'sentry/types/integrations';
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
import {useApiQuery} from 'sentry/utils/queryClient';
import {apiOptions, selectJsonWithHeaders} from 'sentry/utils/api/apiOptions';
import {useLocation} from 'sentry/utils/useLocation';
import {useOrganization} from 'sentry/utils/useOrganization';

Expand All @@ -34,31 +34,24 @@ export function IntegrationRepos(props: Props) {
const {integration} = props;
const organization = useOrganization();
const location = useLocation();
const ENDPOINT = getApiUrl('/organizations/$organizationIdOrSlug/repos/', {
path: {organizationIdOrSlug: organization.slug},
});

const {
data: fetchedItemList,
data: reposResponse,
isPending,
isError,
refetch,
getResponseHeader,
} = useApiQuery<Repository[]>(
[
ENDPOINT,
{
query: {
status: 'active',
integration_id: integration.id,
cursor: location.query.cursor,
},
} = useQuery({
...apiOptions.as<Repository[]>()('/organizations/$organizationIdOrSlug/repos/', {
path: {organizationIdOrSlug: organization.slug},
query: {
status: 'active',
integration_id: integration.id,
cursor: location.query.cursor,
},
],
{
staleTime: 0,
}
);
}),
select: selectJsonWithHeaders,
});
const [itemListState, setItemList] = useState<Repository[]>([]);

if (isPending) {
Expand All @@ -69,6 +62,7 @@ export function IntegrationRepos(props: Props) {
return <LoadingError onRetry={refetch} />;
}

const fetchedItemList = reposResponse?.json ?? [];
const itemList = itemListState.length ? itemListState : fetchedItemList;

// Called by row to signal repository change.
Expand All @@ -91,7 +85,7 @@ export function IntegrationRepos(props: Props) {
setItemList([...itemList, repo]);
};

const itemListPageLinks = getResponseHeader?.('Link') ?? undefined;
const itemListPageLinks = reposResponse?.headers.Link;

return (
<Fragment>
Expand Down
17 changes: 13 additions & 4 deletions static/app/views/settings/projectSeer/addAutofixRepoModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Fragment, useCallback, useMemo, useRef, useState, type ChangeEvent} from 'react';
import styled from '@emotion/styled';
import {useInfiniteQuery} from '@tanstack/react-query';
import {useVirtualizer} from '@tanstack/react-virtual';

import {Alert} from '@sentry/scraps/alert';
Expand All @@ -9,10 +10,14 @@ import {Flex, Stack} from '@sentry/scraps/layout';
import {Link} from '@sentry/scraps/link';

import type {ModalRenderProps} from 'sentry/actionCreators/modal';
import {useOrganizationRepositories} from 'sentry/components/events/autofix/preferences/hooks/useOrganizationRepositories';
import {LoadingIndicator} from 'sentry/components/loadingIndicator';
import {IconSearch} from 'sentry/icons';
import {t, tct, tn} from 'sentry/locale';
import {useFetchAllPages} from 'sentry/utils/api/apiFetch';
import {
organizationRepositoriesInfiniteOptions,
selectUniqueRepos,
} from 'sentry/utils/repositories/repoQueryOptions';
import {useOrganization} from 'sentry/utils/useOrganization';
import {MAX_REPOS_LIMIT} from 'sentry/views/settings/projectSeer/constants';

Expand All @@ -37,10 +42,14 @@ export function AddAutofixRepoModal({
Footer,
closeModal,
}: Props) {
const {data: repositories, isFetching: isFetchingRepositories} =
useOrganizationRepositories();

const organization = useOrganization();

const repositoriesQuery = useInfiniteQuery({
...organizationRepositoriesInfiniteOptions({organization, query: {per_page: 100}}),
select: selectUniqueRepos,
});
useFetchAllPages({result: repositoriesQuery});
const {data: repositories, isFetching: isFetchingRepositories} = repositoriesQuery;
const [modalSearchQuery, setModalSearchQuery] = useState('');
const [showMaxLimitAlert, setShowMaxLimitAlert] = useState(false);
const [modalSelectedRepoIds, setModalSelectedRepoIds] =
Expand Down
15 changes: 12 additions & 3 deletions static/app/views/settings/projectSeer/autofixRepositories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {useCallback, useEffect, useMemo, useState} from 'react';
import {useTheme} from '@emotion/react';
import styled from '@emotion/styled';
import {useInfiniteQuery} from '@tanstack/react-query';

import {Alert} from '@sentry/scraps/alert';
import {Button} from '@sentry/scraps/button';
Expand All @@ -10,7 +11,6 @@ import {Tooltip} from '@sentry/scraps/tooltip';

import {openModal} from 'sentry/actionCreators/modal';
import {DropdownMenu} from 'sentry/components/dropdownMenu';
import {useOrganizationRepositories} from 'sentry/components/events/autofix/preferences/hooks/useOrganizationRepositories';
import {useProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useProjectSeerPreferences';
import {useUpdateProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences';
import type {
Expand All @@ -25,6 +25,11 @@ import {IconAdd} from 'sentry/icons';
import {t, tct} from 'sentry/locale';
import {PluginIcon} from 'sentry/plugins/components/pluginIcon';
import type {Project} from 'sentry/types/project';
import {useFetchAllPages} from 'sentry/utils/api/apiFetch';
import {
organizationRepositoriesInfiniteOptions,
selectUniqueRepos,
} from 'sentry/utils/repositories/repoQueryOptions';
import {useOrganization} from 'sentry/utils/useOrganization';

import {AddAutofixRepoModal} from './addAutofixRepoModal';
Expand All @@ -38,8 +43,12 @@ interface ProjectSeerProps {
export function AutofixRepositories({project}: ProjectSeerProps) {
const theme = useTheme();
const organization = useOrganization();
const {data: repositories, isFetching: isFetchingRepositories} =
useOrganizationRepositories();
const repositoriesQuery = useInfiniteQuery({
...organizationRepositoriesInfiniteOptions({organization, query: {per_page: 100}}),
select: selectUniqueRepos,
});
useFetchAllPages({result: repositoriesQuery});
const {data: repositories, isFetching: isFetchingRepositories} = repositoriesQuery;
const {
preference,
codeMappingRepos,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {useMemo} from 'react';
import styled from '@emotion/styled';
import {useInfiniteQuery} from '@tanstack/react-query';
import seerConfigBug1 from 'getsentry-images/spot/seer-config-bug-1.svg';

import {Button} from '@sentry/scraps/button';
Expand All @@ -9,7 +10,6 @@ import {Heading} from '@sentry/scraps/text';

import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
import {openModal} from 'sentry/actionCreators/modal';
import {useOrganizationRepositories} from 'sentry/components/events/autofix/preferences/hooks/useOrganizationRepositories';
import {useUpdateProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences';
import type {
ProjectSeerPreferences,
Expand All @@ -22,6 +22,11 @@ import {IconAdd} from 'sentry/icons/iconAdd';
import {t, tct} from 'sentry/locale';
import type {Organization} from 'sentry/types/organization';
import type {Project} from 'sentry/types/project';
import {useFetchAllPages} from 'sentry/utils/api/apiFetch';
import {
organizationRepositoriesInfiniteOptions,
selectUniqueRepos,
} from 'sentry/utils/repositories/repoQueryOptions';
import {useOrganization} from 'sentry/utils/useOrganization';
import {AddAutofixRepoModal} from 'sentry/views/settings/projectSeer/addAutofixRepoModal';

Expand Down Expand Up @@ -58,8 +63,12 @@ const getTableHeaders = (organization: Organization): React.ReactNode[] => [
export function AutofixRepositories({canWrite, preference, project}: Props) {
const organization = useOrganization();

const {data: repositories, isFetching: isFetchingRepositories} =
useOrganizationRepositories();
const repositoriesQuery = useInfiniteQuery({
...organizationRepositoriesInfiniteOptions({organization, query: {per_page: 100}}),
select: selectUniqueRepos,
});
useFetchAllPages({result: repositoriesQuery});
const {data: repositories, isFetching: isFetchingRepositories} = repositoriesQuery;

const {mutate: updateProjectSeerPreferences} = useUpdateProjectSeerPreferences(project);

Expand Down
Loading
Loading