import {Fragment, useContext, useEffect, useRef} from 'react'; import styled from '@emotion/styled'; import FormContext from 'sentry/components/forms/formContext'; import type {FieldValue} from 'sentry/components/forms/model'; import Panel from 'sentry/components/panels/panel'; import Placeholder from 'sentry/components/placeholder'; import {t} from 'sentry/locale'; import {useApiQuery} from 'sentry/utils/queryClient'; import {useDimensions} from 'sentry/utils/useDimensions'; import useOrganization from 'sentry/utils/useOrganization'; import {MockCheckInTimeline} from 'sentry/views/monitors/components/overviewTimeline/checkInTimeline'; import { GridLineOverlay, GridLineTimeLabels, } from 'sentry/views/monitors/components/overviewTimeline/gridLines'; import {TimelinePlaceholder} from 'sentry/views/monitors/components/overviewTimeline/timelinePlaceholder'; import {getConfigFromTimeRange} from 'sentry/views/monitors/components/overviewTimeline/utils'; import {ScheduleType} from 'sentry/views/monitors/types'; interface ScheduleConfig { cronSchedule?: FieldValue; intervalFrequency?: FieldValue; intervalUnit?: FieldValue; scheduleType?: FieldValue; } const NUM_SAMPLE_TICKS = 9; function isValidConfig(schedule: ScheduleConfig) { const {scheduleType, cronSchedule, intervalFrequency, intervalUnit} = schedule; return !!( (scheduleType === ScheduleType.CRONTAB && cronSchedule) || (scheduleType === ScheduleType.INTERVAL && intervalFrequency && intervalUnit) ); } interface Props { schedule: ScheduleConfig; } export function MockTimelineVisualization({schedule}: Props) { const {scheduleType, cronSchedule, intervalFrequency, intervalUnit} = schedule; const organization = useOrganization(); const {form} = useContext(FormContext); const query = { num_ticks: NUM_SAMPLE_TICKS, schedule_type: scheduleType, schedule: scheduleType === 'interval' ? [intervalFrequency, intervalUnit] : cronSchedule, }; const elementRef = useRef(null); const {width: timelineWidth} = useDimensions({elementRef}); const sampleDataQueryKey = [ `/organizations/${organization.slug}/monitors-schedule-data/`, {query}, ] as const; const {data, isLoading, isError, error} = useApiQuery(sampleDataQueryKey, { staleTime: 0, enabled: isValidConfig(schedule), retry: false, }); const errorMessage = isError || !isValidConfig(schedule) ? error?.responseJSON?.schedule?.[0] ?? t('Invalid Schedule') : null; useEffect(() => { if (!form) { return; } if (scheduleType === ScheduleType.INTERVAL) { form.setError('config.schedule.frequency', errorMessage); } else if (scheduleType === ScheduleType.CRONTAB) { form.setError('config.schedule', errorMessage); } }, [errorMessage, form, scheduleType]); const mockTimestamps = data?.map(ts => new Date(ts * 1000)); const start = mockTimestamps?.[0]; const end = mockTimestamps?.[mockTimestamps.length - 1]; const timeWindowConfig = start && end ? getConfigFromTimeRange(start, end, timelineWidth) : undefined; return ( {isLoading || !start || !end || !timeWindowConfig || !mockTimestamps ? ( {errorMessage ? ( ) : ( )} ) : ( )} ); } const TimelineContainer = styled(Panel)` display: grid; grid-template-columns: 1fr; grid-template-rows: 40px 100px; align-items: center; `; const StyledGridLineTimeLabels = styled(GridLineTimeLabels)` grid-column: 0; `; const StyledGridLineOverlay = styled(GridLineOverlay)` grid-column: 0; `; const TimelineWidthTracker = styled('div')` position: absolute; width: 100%; grid-row: 1; grid-column: 0; `;