diff --git a/static/app/views/issueDetails/groupEventAttachments/groupEventAttachments.tsx b/static/app/views/issueDetails/groupEventAttachments/groupEventAttachments.tsx index c42d1716cd3c3b..decf5e1bb058a9 100644 --- a/static/app/views/issueDetails/groupEventAttachments/groupEventAttachments.tsx +++ b/static/app/views/issueDetails/groupEventAttachments/groupEventAttachments.tsx @@ -67,11 +67,10 @@ export function GroupEventAttachments({project, group}: GroupEventAttachmentsPro } }, [previouslyUsedAttachmentsTab, location, navigate]); - const {attachments, isPending, isError, getResponseHeader, refetch} = - useGroupEventAttachments({ - group, - activeAttachmentsTab, - }); + const {attachments, isPending, isError, pageLinks, refetch} = useGroupEventAttachments({ + group, + activeAttachmentsTab, + }); const {mutate: deleteAttachment} = useDeleteGroupEventAttachment(); @@ -162,7 +161,7 @@ export function GroupEventAttachments({project, group}: GroupEventAttachmentsPro {activeAttachmentsTab === EventAttachmentFilter.SCREENSHOT ? renderScreenshotGallery() : renderAttachmentsTable()} - + ); } diff --git a/static/app/views/issueDetails/groupEventAttachments/useDeleteGroupEventAttachment.tsx b/static/app/views/issueDetails/groupEventAttachments/useDeleteGroupEventAttachment.tsx index 1169d8478e92fa..bf7cbf2de6a729 100644 --- a/static/app/views/issueDetails/groupEventAttachments/useDeleteGroupEventAttachment.tsx +++ b/static/app/views/issueDetails/groupEventAttachments/useDeleteGroupEventAttachment.tsx @@ -1,26 +1,24 @@ +import {useMutation, useQueryClient} from '@tanstack/react-query'; + import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator'; import {t} from 'sentry/locale'; import type {IssueAttachment} from 'sentry/types/group'; -import { - getApiQueryData, - setApiQueryData, - useMutation, - useQueryClient, -} from 'sentry/utils/queryClient'; +import type {ApiResponse} from 'sentry/utils/api/apiFetch'; import type {RequestError} from 'sentry/utils/requestError/requestError'; import {useApi} from 'sentry/utils/useApi'; -import {makeFetchGroupEventAttachmentsQueryKey} from './useGroupEventAttachments'; +import { + fetchGroupEventAttachmentsApiOptions, + type FetchGroupEventAttachmentsApiOptionsParams, +} from './useGroupEventAttachments'; -type DeleteGroupEventAttachmentVariables = Parameters< - typeof makeFetchGroupEventAttachmentsQueryKey ->[0] & { +type DeleteGroupEventAttachmentVariables = FetchGroupEventAttachmentsApiOptionsParams & { attachment: IssueAttachment; projectSlug: string; }; type DeleteGroupEventAttachmentContext = { - previous?: IssueAttachment[]; + previous?: ApiResponse; }; export function useDeleteGroupEventAttachment() { @@ -41,28 +39,24 @@ export function useDeleteGroupEventAttachment() { } ), onMutate: async variables => { - await queryClient.cancelQueries({ - queryKey: makeFetchGroupEventAttachmentsQueryKey(variables), - }); + const {queryKey} = fetchGroupEventAttachmentsApiOptions(variables); - const previous = getApiQueryData( - queryClient, - makeFetchGroupEventAttachmentsQueryKey(variables) - ); + await queryClient.cancelQueries({queryKey}); - setApiQueryData( - queryClient, - makeFetchGroupEventAttachmentsQueryKey(variables), - oldData => { - if (!Array.isArray(oldData)) { - return oldData; - } + const previous = queryClient.getQueryData>(queryKey); - return oldData.filter( - oldAttachment => oldAttachment.id !== variables.attachment.id - ); + queryClient.setQueryData>(queryKey, oldData => { + if (!oldData) { + return oldData; } - ); + + return { + ...oldData, + json: oldData.json.filter( + oldAttachment => oldAttachment.id !== variables.attachment.id + ), + }; + }); return {previous}; }, @@ -77,11 +71,8 @@ export function useDeleteGroupEventAttachment() { ); if (context) { - setApiQueryData( - queryClient, - makeFetchGroupEventAttachmentsQueryKey(variables), - context.previous - ); + const {queryKey} = fetchGroupEventAttachmentsApiOptions(variables); + queryClient.setQueryData(queryKey, context.previous); } }, }); diff --git a/static/app/views/issueDetails/groupEventAttachments/useGroupEventAttachments.tsx b/static/app/views/issueDetails/groupEventAttachments/useGroupEventAttachments.tsx index 00b6e9b93cae9b..7cab4ca24505d0 100644 --- a/static/app/views/issueDetails/groupEventAttachments/useGroupEventAttachments.tsx +++ b/static/app/views/issueDetails/groupEventAttachments/useGroupEventAttachments.tsx @@ -1,11 +1,9 @@ +import {useQuery} from '@tanstack/react-query'; + import type {DateString} from 'sentry/types/core'; import type {Group, IssueAttachment} from 'sentry/types/group'; -import {getApiUrl} from 'sentry/utils/api/getApiUrl'; -import { - useApiQuery, - type ApiQueryKey, - type UseApiQueryOptions, -} from 'sentry/utils/queryClient'; +import {apiOptions, selectJsonWithHeaders} from 'sentry/utils/api/apiOptions'; +import {keepPreviousData} from 'sentry/utils/queryClient'; import {useLocation} from 'sentry/utils/useLocation'; import {useOrganization} from 'sentry/utils/useOrganization'; import {useEventQuery} from 'sentry/views/issueDetails/streamline/hooks/useEventQuery'; @@ -20,20 +18,10 @@ interface UseGroupEventAttachmentsOptions { * current filters (for environment, date, query, etc). */ fetchAllAvailable?: boolean; - placeholderData?: UseApiQueryOptions['placeholderData']; + placeholderData?: typeof keepPreviousData; }; } -interface MakeFetchGroupEventAttachmentsQueryKeyOptions extends UseGroupEventAttachmentsOptions { - cursor: string | undefined; - environment: string[] | string | undefined; - orgSlug: string; - end?: DateString; - eventQuery?: string; - start?: DateString; - statsPeriod?: string; -} - type GroupEventAttachmentsTypeFilter = | 'event.minidump' | 'event.applecrashreport' @@ -51,7 +39,19 @@ interface GroupEventAttachmentsQuery { types?: GroupEventAttachmentsTypeFilter | GroupEventAttachmentsTypeFilter[]; } -export const makeFetchGroupEventAttachmentsQueryKey = ({ +export interface FetchGroupEventAttachmentsApiOptionsParams { + activeAttachmentsTab: 'all' | 'onlyCrash' | 'screenshot'; + group: Group; + orgSlug: string; + cursor?: string; + end?: DateString; + environment?: string[] | string; + eventQuery?: string; + start?: DateString; + statsPeriod?: string; +} + +export function fetchGroupEventAttachmentsApiOptions({ activeAttachmentsTab, group, orgSlug, @@ -61,7 +61,7 @@ export const makeFetchGroupEventAttachmentsQueryKey = ({ start, end, statsPeriod, -}: MakeFetchGroupEventAttachmentsQueryKeyOptions): ApiQueryKey => { +}: FetchGroupEventAttachmentsApiOptionsParams) { const query: GroupEventAttachmentsQuery = {}; if (environment) { @@ -94,13 +94,15 @@ export const makeFetchGroupEventAttachmentsQueryKey = ({ query.types = ['event.minidump', 'event.applecrashreport']; } - return [ - getApiUrl('/organizations/$organizationIdOrSlug/issues/$issueId/attachments/', { + return apiOptions.as()( + '/organizations/$organizationIdOrSlug/issues/$issueId/attachments/', + { path: {organizationIdOrSlug: orgSlug, issueId: group.id}, - }), - {query}, - ]; -}; + query, + staleTime: 60_000, + } + ); +} export function useGroupEventAttachments({ group, @@ -115,14 +117,9 @@ export function useGroupEventAttachments({ const hasSetStatsPeriod = location.query.statsPeriod || location.query.start || location.query.end; const fetchAllAvailable = options?.fetchAllAvailable; - const { - data: attachments = [], - isPending, - isError, - getResponseHeader, - refetch, - } = useApiQuery( - makeFetchGroupEventAttachmentsQueryKey({ + + const {data, isPending, isError, refetch} = useQuery({ + ...fetchGroupEventAttachmentsApiOptions({ activeAttachmentsTab, group, orgSlug: organization.slug, @@ -135,13 +132,15 @@ export function useGroupEventAttachments({ fetchAllAvailable && !hasSetStatsPeriod ? undefined : eventView.statsPeriod, eventQuery: fetchAllAvailable ? undefined : eventQuery, }), - {placeholderData: options?.placeholderData, staleTime: 60_000} - ); + placeholderData: options?.placeholderData, + select: selectJsonWithHeaders, + }); + return { - attachments, + attachments: data?.json ?? [], isPending, isError, - getResponseHeader, + pageLinks: data?.headers.Link ?? null, refetch, }; } diff --git a/static/app/views/issueDetails/streamline/eventNavigation.tsx b/static/app/views/issueDetails/streamline/eventNavigation.tsx index ce65213c9055ad..089de174b5080e 100644 --- a/static/app/views/issueDetails/streamline/eventNavigation.tsx +++ b/static/app/views/issueDetails/streamline/eventNavigation.tsx @@ -98,9 +98,7 @@ export function IssueEventNavigation({event, group}: IssueEventNavigationProps) options: {placeholderData: keepPreviousData}, }); - const attachmentPagination = parseLinkHeader( - attachments.getResponseHeader?.('Link') ?? null - ); + const attachmentPagination = parseLinkHeader(attachments.pageLinks); // Since we reuse whatever page the user was on, we can look at pagination to determine if there are more attachments const hasManyAttachments = attachmentPagination.next?.results || attachmentPagination.previous?.results; diff --git a/static/app/views/issueDetails/streamline/header/attachmentsBadge.tsx b/static/app/views/issueDetails/streamline/header/attachmentsBadge.tsx index e37e62abe4fcfe..bb6548d40f53a4 100644 --- a/static/app/views/issueDetails/streamline/header/attachmentsBadge.tsx +++ b/static/app/views/issueDetails/streamline/header/attachmentsBadge.tsx @@ -23,9 +23,7 @@ export function AttachmentsBadge({group}: {group: Group}) { options: {placeholderData: keepPreviousData, fetchAllAvailable: true}, }); - const attachmentPagination = parseLinkHeader( - attachments.getResponseHeader?.('Link') ?? null - ); + const attachmentPagination = parseLinkHeader(attachments.pageLinks); // Since we reuse whatever page the user was on, we can look at pagination to determine if there are more attachments const hasManyAttachments =