import {Fragment, useCallback, useState} from 'react'; import styled from '@emotion/styled'; import sortBy from 'lodash/sortBy'; import { deleteMonitorProcessingErrorByType, updateMonitor, } from 'sentry/actionCreators/monitors'; import Alert from 'sentry/components/alert'; import * as Layout from 'sentry/components/layouts/thirds'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import {DatePageFilter} from 'sentry/components/organizations/datePageFilter'; import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; import {setApiQueryData, useApiQuery, useQueryClient} from 'sentry/utils/queryClient'; import useApi from 'sentry/utils/useApi'; import useOrganization from 'sentry/utils/useOrganization'; import DetailsSidebar from 'sentry/views/monitors/components/detailsSidebar'; import {DetailsTimeline} from 'sentry/views/monitors/components/detailsTimeline'; import MonitorProcessingErrors from 'sentry/views/monitors/components/processingErrors/monitorProcessingErrors'; import {makeMonitorErrorsQueryKey} from 'sentry/views/monitors/components/processingErrors/utils'; import {makeMonitorDetailsQueryKey} from 'sentry/views/monitors/utils'; import MonitorCheckIns from './components/monitorCheckIns'; import MonitorHeader from './components/monitorHeader'; import MonitorIssues from './components/monitorIssues'; import MonitorStats from './components/monitorStats'; import MonitorOnboarding from './components/onboarding'; import {StatusToggleButton} from './components/statusToggleButton'; import type {MonitorBucket} from './components/timeline/types'; import type {CheckinProcessingError, Monitor, ProcessingErrorType} from './types'; const DEFAULT_POLL_INTERVAL_MS = 5000; type Props = RouteComponentProps<{monitorSlug: string; projectId: string}, {}>; function hasLastCheckIn(monitor: Monitor) { return monitor.environments.some(e => e.lastCheckIn); } function MonitorDetails({params, location}: Props) { const api = useApi(); const organization = useOrganization(); const queryClient = useQueryClient(); const queryKey = makeMonitorDetailsQueryKey( organization, params.projectId, params.monitorSlug, { environment: location.query.environment, } ); const {data: monitor, isError} = useApiQuery(queryKey, { staleTime: 0, refetchOnWindowFocus: true, // Refetches while we are waiting for the user to send their first check-in refetchInterval: query => { if (!query.state.data) { return false; } const [monitorData] = query.state.data; return hasLastCheckIn(monitorData) ? false : DEFAULT_POLL_INTERVAL_MS; }, }); const {data: checkinErrors, refetch: refetchErrors} = useApiQuery< CheckinProcessingError[] >(makeMonitorErrorsQueryKey(organization, params.projectId, params.monitorSlug), { staleTime: 0, refetchOnWindowFocus: true, }); function onUpdate(data: Monitor) { const updatedMonitor = { ...data, // TODO(davidenwang): This is a bit of a hack, due to the PUT request // which pauses/unpauses a monitor not returning monitor environments // we should reuse the environments retrieved from the initial request environments: monitor?.environments, }; setApiQueryData(queryClient, queryKey, updatedMonitor); } const handleUpdate = async (data: Partial) => { if (monitor === undefined) { return; } const resp = await updateMonitor(api, organization.slug, monitor, data); if (resp !== null) { onUpdate(resp); } }; function handleDismissError(errortype: ProcessingErrorType) { deleteMonitorProcessingErrorByType( api, organization.slug, params.projectId, params.monitorSlug, errortype ); refetchErrors(); } // Only display the unknown legend when there are visible unknown check-ins // in the timeline const [showUnknownLegend, setShowUnknownLegend] = useState(false); const checkHasUnknown = useCallback((stats: MonitorBucket[]) => { const hasUnknown = stats.some(bucket => Object.values(bucket[1]).some(envBucket => Boolean(envBucket.unknown)) ); setShowUnknownLegend(hasUnknown); }, []); if (isError) { return ( ); } if (!monitor) { return ( ); } const envsSortedByLastCheck = sortBy(monitor.environments, e => e.lastCheckIn); return ( {monitor.status === 'disabled' && ( handleUpdate({status})} > {t('Enable')} } > {t('This monitor is disabled and is not accepting check-ins.')} )} {!!checkinErrors?.length && ( {t('Errors were encountered while ingesting check-ins for this monitor')} )} {!hasLastCheckIn(monitor) ? ( ) : ( )} ); } const StyledPageFilterBar = styled(PageFilterBar)` margin-bottom: ${space(2)}; `; export default MonitorDetails;