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
41 changes: 24 additions & 17 deletions static/app/components/modals/bulkEditMonitorsModal.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand All @@ -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 {}
Expand All @@ -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,
});
Expand Down Expand Up @@ -84,24 +87,28 @@ export function BulkEditMonitorsModal({Header, Body, Footer, closeModal}: Props)
setSelectedMonitors([]);

if (resp?.updated) {
setApiQueryData<Monitor[]>(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<Monitor[]>(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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ 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';
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';
Expand Down Expand Up @@ -53,14 +53,16 @@ export function OverviewTimeline({monitorList}: Props) {
return;
Comment thread
TkDodo marked this conversation as resolved.
}

const queryKey = makeMonitorListQueryKey(organization, location.query);
setApiQueryData<Monitor[]>(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]!;
Expand All @@ -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]};
});
};

Expand All @@ -98,11 +100,14 @@ export function OverviewTimeline({monitorList}: Props) {
return;
}

const queryKey = makeMonitorListQueryKey(organization, location.query);
setApiQueryData<Monitor[]>(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;
});
};
Expand All @@ -115,12 +120,15 @@ export function OverviewTimeline({monitorList}: Props) {
return;
}

const queryKey = makeMonitorListQueryKey(organization, location.query);
setApiQueryData<Monitor[]>(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;
});
};
Expand Down
46 changes: 24 additions & 22 deletions static/app/views/insights/crons/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>
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<Monitor[]>()('/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(
Expand Down
29 changes: 16 additions & 13 deletions static/app/views/insights/crons/views/overview.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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';
Expand All @@ -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',
Expand All @@ -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<Monitor[]>(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};
Expand Down
Loading