diff --git a/static/app/components/modals/bulkEditMonitorsModal.tsx b/static/app/components/modals/bulkEditMonitorsModal.tsx index 9c7b307f5189fd..8d3dc88c1d60bb 100644 --- a/static/app/components/modals/bulkEditMonitorsModal.tsx +++ b/static/app/components/modals/bulkEditMonitorsModal.tsx @@ -1,6 +1,7 @@ import {Fragment, useState} from 'react'; import {css} from '@emotion/react'; import styled from '@emotion/styled'; +import {useQuery, useQueryClient} from '@tanstack/react-query'; import {Button} from '@sentry/scraps/button'; import {Checkbox} from '@sentry/scraps/checkbox'; @@ -15,7 +16,7 @@ import {PanelTable} from 'sentry/components/panels/panelTable'; import {Placeholder} from 'sentry/components/placeholder'; import {SearchBar} from 'sentry/components/searchBar'; import {t, tct, tn} from 'sentry/locale'; -import {setApiQueryData, useApiQuery, useQueryClient} from 'sentry/utils/queryClient'; +import {selectJsonWithHeaders} from 'sentry/utils/api/apiOptions'; import {useApi} from 'sentry/utils/useApi'; import {useLocation} from 'sentry/utils/useLocation'; import {useOrganization} from 'sentry/utils/useOrganization'; @@ -25,7 +26,7 @@ import { SortSelector, } from 'sentry/views/insights/crons/components/overviewTimeline/sortSelector'; import type {Monitor} from 'sentry/views/insights/crons/types'; -import {makeMonitorListQueryKey} from 'sentry/views/insights/crons/utils'; +import {monitorListApiOptions} from 'sentry/views/insights/crons/utils'; import {scheduleAsText} from 'sentry/views/insights/crons/utils/scheduleAsText'; interface Props extends ModalRenderProps {} @@ -46,10 +47,12 @@ export function BulkEditMonitorsModal({Header, Body, Footer, closeModal}: Props) sort: MonitorSortOption; }>({sort: MonitorSortOption.STATUS, order: MonitorSortOrder.ASCENDING}); - const queryKey = makeMonitorListQueryKey(organization, { - ...location.query, - query: searchQuery, + const monitorListOptions = monitorListApiOptions(organization, { cursor, + query: searchQuery, + project: location.query.project, + environment: location.query.environment, + owner: location.query.owner, sort: sortSelection.sort, asc: sortSelection.order, }); @@ -84,24 +87,28 @@ export function BulkEditMonitorsModal({Header, Body, Footer, closeModal}: Props) setSelectedMonitors([]); if (resp?.updated) { - setApiQueryData(queryClient, queryKey, oldMonitorList => { - return oldMonitorList?.map( - monitor => - resp.updated.find(newMonitor => newMonitor.slug === monitor.slug) ?? monitor - ); + queryClient.setQueryData(monitorListOptions.queryKey, previous => { + if (!previous) { + return previous; + } + return { + ...previous, + json: previous.json.map( + monitor => + resp.updated.find(newMonitor => newMonitor.slug === monitor.slug) ?? monitor + ), + }; }); } setIsUpdating(false); }; - const { - data: monitorList, - getResponseHeader: monitorListHeaders, - isPending, - } = useApiQuery(queryKey, { - staleTime: 0, + const {data, isPending} = useQuery({ + ...monitorListOptions, + select: selectJsonWithHeaders, }); - const monitorPageLinks = monitorListHeaders?.('Link'); + const monitorList = data?.json; + const monitorPageLinks = data?.headers.Link; const headers = [t('Monitor'), t('State'), t('Muted'), t('Schedule')]; const shouldDisable = selectedMonitors.every(monitor => monitor.status !== 'disabled'); diff --git a/static/app/views/insights/crons/components/overviewTimeline/index.tsx b/static/app/views/insights/crons/components/overviewTimeline/index.tsx index f4eb07a61dd5ef..df442cb979bb4d 100644 --- a/static/app/views/insights/crons/components/overviewTimeline/index.tsx +++ b/static/app/views/insights/crons/components/overviewTimeline/index.tsx @@ -17,7 +17,7 @@ import {useDateNavigation} from 'sentry/components/checkInTimeline/hooks/useDate import {useTimeWindowConfig} from 'sentry/components/checkInTimeline/hooks/useTimeWindowConfig'; import {Panel} from 'sentry/components/panels/panel'; import {Sticky} from 'sentry/components/sticky'; -import {setApiQueryData, useQueryClient} from 'sentry/utils/queryClient'; +import {useQueryClient} from 'sentry/utils/queryClient'; import {useApi} from 'sentry/utils/useApi'; import {useDebouncedValue} from 'sentry/utils/useDebouncedValue'; import {useDimensions} from 'sentry/utils/useDimensions'; @@ -25,7 +25,7 @@ import {useLocation} from 'sentry/utils/useLocation'; import {useOrganization} from 'sentry/utils/useOrganization'; import {CronServiceIncidents} from 'sentry/views/insights/crons/components/serviceIncidents'; import type {Monitor} from 'sentry/views/insights/crons/types'; -import {makeMonitorListQueryKey} from 'sentry/views/insights/crons/utils'; +import {monitorListApiOptions} from 'sentry/views/insights/crons/utils'; import {OverviewRow} from './overviewRow'; import {SortSelector} from './sortSelector'; @@ -53,14 +53,16 @@ export function OverviewTimeline({monitorList}: Props) { return; } - const queryKey = makeMonitorListQueryKey(organization, location.query); - setApiQueryData(queryClient, queryKey, oldMonitorList => { - if (!oldMonitorList) { + const monitorListOptions = monitorListApiOptions(organization, location.query); + + queryClient.setQueryData(monitorListOptions.queryKey, old => { + if (!old) { return undefined; } + const oldMonitorList = old.json; const oldMonitorIdx = oldMonitorList.findIndex(m => m.slug === monitor.slug); if (oldMonitorIdx < 0) { - return oldMonitorList; + return old; } const oldMonitor = oldMonitorList[oldMonitorIdx]!; @@ -74,10 +76,10 @@ export function OverviewTimeline({monitorList}: Props) { const right = oldMonitorList.slice(oldMonitorIdx + 1); if (newEnvList.length === 0) { - return [...left, ...right]; + return {...old, json: [...left, ...right]}; } - return [...left, updatedMonitor, ...right]; + return {...old, json: [...left, updatedMonitor, ...right]}; }); }; @@ -98,11 +100,14 @@ export function OverviewTimeline({monitorList}: Props) { return; } - const queryKey = makeMonitorListQueryKey(organization, location.query); - setApiQueryData(queryClient, queryKey, oldMonitorList => { - return oldMonitorList - ? // TODO(davidenwang): in future only change the specifically modified environment for optimistic updates - oldMonitorList.map(m => (m.slug === monitor.slug ? resp : m)) + const monitorListOptions = monitorListApiOptions(organization, location.query); + queryClient.setQueryData(monitorListOptions.queryKey, old => { + return old + ? { + ...old, + // TODO(davidenwang): in future only change the specifically modified environment for optimistic updates + json: old.json.map(m => (m.slug === monitor.slug ? resp : m)), + } : undefined; }); }; @@ -115,12 +120,15 @@ export function OverviewTimeline({monitorList}: Props) { return; } - const queryKey = makeMonitorListQueryKey(organization, location.query); - setApiQueryData(queryClient, queryKey, oldMonitorList => { - return oldMonitorList - ? oldMonitorList.map(m => - m.slug === monitor.slug ? {...m, status: resp.status} : m - ) + const monitorListOptions = monitorListApiOptions(organization, location.query); + queryClient.setQueryData(monitorListOptions.queryKey, old => { + return old + ? { + ...old, + json: old.json.map(m => + m.slug === monitor.slug ? {...m, status: resp.status} : m + ), + } : undefined; }); }; diff --git a/static/app/views/insights/crons/utils.tsx b/static/app/views/insights/crons/utils.tsx index d5da279e703e8e..ec11d6386f2ecc 100644 --- a/static/app/views/insights/crons/utils.tsx +++ b/static/app/views/insights/crons/utils.tsx @@ -2,34 +2,36 @@ import type {TickStyle} from 'sentry/components/checkInTimeline/types'; import {t, tn} from 'sentry/locale'; import type {SelectValue} from 'sentry/types/core'; import type {Organization} from 'sentry/types/organization'; +import {apiOptions} from 'sentry/utils/api/apiOptions'; import {getApiUrl} from 'sentry/utils/api/getApiUrl'; -import {CheckInStatus} from './types'; +import {CheckInStatus, type Monitor} from './types'; -export function makeMonitorListQueryKey( +export function monitorListApiOptions( organization: Organization, - params: Record + queryParams: Partial< + Record< + 'asc' | 'cursor' | 'environment' | 'owner' | 'project' | 'query' | 'sort', + unknown + > + > ) { - const {query, project, environment, owner, cursor, sort, asc} = params; - - return [ - getApiUrl('/organizations/$organizationIdOrSlug/monitors/', { - path: {organizationIdOrSlug: organization.slug}, - }), - { - query: { - cursor, - query, - project, - environment, - owner, - includeNew: true, - per_page: 20, - sort, - asc, - }, + const {query, project, environment, owner, cursor, sort, asc} = queryParams; + return apiOptions.as()('/organizations/$organizationIdOrSlug/monitors/', { + path: {organizationIdOrSlug: organization.slug}, + query: { + cursor, + query, + project, + environment, + owner, + includeNew: true, + per_page: 20, + sort, + asc, }, - ] as const; + staleTime: 0, + }); } export function makeMonitorDetailsQueryKey( diff --git a/static/app/views/insights/crons/views/overview.tsx b/static/app/views/insights/crons/views/overview.tsx index d2247d63559dcb..356948e99497d4 100644 --- a/static/app/views/insights/crons/views/overview.tsx +++ b/static/app/views/insights/crons/views/overview.tsx @@ -1,5 +1,6 @@ import {Fragment} from 'react'; import styled from '@emotion/styled'; +import {useQuery} from '@tanstack/react-query'; import * as qs from 'query-string'; import {Alert} from '@sentry/scraps/alert'; @@ -28,7 +29,7 @@ import {SearchBar} from 'sentry/components/searchBar'; import {SentryDocumentTitle} from 'sentry/components/sentryDocumentTitle'; import {IconAdd, IconList} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; -import {useApiQuery} from 'sentry/utils/queryClient'; +import {selectJsonWithHeaders} from 'sentry/utils/api/apiOptions'; import {decodeList, decodeScalar} from 'sentry/utils/queryString'; import {useRouteAnalyticsEventNames} from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames'; import {useRouteAnalyticsParams} from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams'; @@ -43,8 +44,7 @@ import {OwnerFilter} from 'sentry/views/insights/crons/components/ownerFilter'; import {GlobalMonitorProcessingErrors} from 'sentry/views/insights/crons/components/processingErrors/globalMonitorProcessingErrors'; import {useCronsUpsertGuideState} from 'sentry/views/insights/crons/components/useCronsUpsertGuideState'; import {MODULE_DESCRIPTION, MODULE_DOC_LINK} from 'sentry/views/insights/crons/settings'; -import type {Monitor} from 'sentry/views/insights/crons/types'; -import {makeMonitorListQueryKey} from 'sentry/views/insights/crons/utils'; +import {monitorListApiOptions} from 'sentry/views/insights/crons/utils'; const CronsListPageHeader = HookOrDefault({ hookName: 'component:crons-list-page-header', @@ -57,21 +57,24 @@ function CronsOverview() { const {guideVisible} = useCronsUpsertGuideState(); const project = decodeList(location.query?.project); - const queryKey = makeMonitorListQueryKey(organization, location.query); - - const { - data: monitorList, - getResponseHeader: monitorListHeaders, - isPending, - refetch, - } = useApiQuery(queryKey, { - staleTime: 0, + const {data, isPending, refetch} = useQuery({ + ...monitorListApiOptions(organization, { + cursor: location.query.cursor, + query: location.query.query, + project: location.query.project, + environment: location.query.environment, + owner: location.query.owner, + sort: location.query.sort, + asc: location.query.asc, + }), + select: selectJsonWithHeaders, }); + const monitorList = data?.json; useRouteAnalyticsEventNames('monitors.page_viewed', 'Monitors: Page Viewed'); useRouteAnalyticsParams({empty_state: !monitorList || monitorList.length === 0}); - const monitorListPageLinks = monitorListHeaders?.('Link'); + const monitorListPageLinks = data?.headers.Link; const handleSearch = (query: string) => { const currentQuery = {...location.query, cursor: undefined};