import {Fragment} from 'react'; import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import moment from 'moment-timezone'; import {Alert} from 'sentry/components/core/alert'; import * as Layout from 'sentry/components/layouts/thirds'; import Link from 'sentry/components/links/link'; import Panel from 'sentry/components/panels/panel'; import PanelBody from 'sentry/components/panels/panelBody'; import Placeholder from 'sentry/components/placeholder'; import type {ChangeData} from 'sentry/components/timeRangeSelector'; import {TimeRangeSelector} from 'sentry/components/timeRangeSelector'; import {Tooltip} from 'sentry/components/tooltip'; import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {RuleActionsCategories} from 'sentry/types/alerts'; import type {Project} from 'sentry/types/project'; import {shouldShowOnDemandMetricAlertUI} from 'sentry/utils/onDemandMetrics/features'; import {useLocation} from 'sentry/utils/useLocation'; import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; import AnomalyDetectionFeedbackBanner from 'sentry/views/alerts/rules/metric/details/anomalyDetectionFeedbackBanner'; import {ErrorMigrationWarning} from 'sentry/views/alerts/rules/metric/details/errorMigrationWarning'; import MetricHistory from 'sentry/views/alerts/rules/metric/details/metricHistory'; import type {MetricRule} from 'sentry/views/alerts/rules/metric/types'; import { AlertRuleComparisonType, Dataset, TimePeriod, } from 'sentry/views/alerts/rules/metric/types'; import {extractEventTypeFilterFromRule} from 'sentry/views/alerts/rules/metric/utils/getEventTypeFilter'; import {isOnDemandMetricAlert} from 'sentry/views/alerts/rules/metric/utils/onDemandMetricAlert'; import {getAlertRuleActionCategory} from 'sentry/views/alerts/rules/utils'; import type {Anomaly, Incident} from 'sentry/views/alerts/types'; import {AlertRuleStatus} from 'sentry/views/alerts/types'; import {alertDetailsLink} from 'sentry/views/alerts/utils'; import {isCrashFreeAlert} from '../utils/isCrashFreeAlert'; import type {TimePeriodType} from './constants'; import {SELECTOR_RELATIVE_PERIODS} from './constants'; import MetricChart from './metricChart'; import RelatedIssues from './relatedIssues'; import RelatedTransactions from './relatedTransactions'; import {MetricDetailsSidebar} from './sidebar'; import {getFilter, getPeriodInterval} from './utils'; export interface MetricDetailsBodyProps { timePeriod: TimePeriodType; anomalies?: Anomaly[]; incidents?: Incident[]; project?: Project; rule?: MetricRule; selectedIncident?: Incident | null; } export default function MetricDetailsBody({ project, rule, incidents, timePeriod, selectedIncident, anomalies, }: MetricDetailsBodyProps) { const theme = useTheme(); const organization = useOrganization(); const location = useLocation(); const navigate = useNavigate(); const handleTimePeriodChange = (datetime: ChangeData) => { const {start, end, relative} = datetime; if (start && end) { return navigate({ ...location, query: { start: moment(start).utc().format(), end: moment(end).utc().format(), }, }); } return navigate({ ...location, query: { period: relative, }, }); }; if (!rule || !project) { return ( ); } const {dataset, aggregate, query} = rule; const eventType = extractEventTypeFilterFromRule(rule); const queryWithTypeFilter = dataset === Dataset.EVENTS_ANALYTICS_PLATFORM ? query : (query ? `(${query}) AND (${eventType})` : eventType).trim(); const relativeOptions = { ...SELECTOR_RELATIVE_PERIODS, ...(rule.timeWindow > 1 ? {[TimePeriod.FOURTEEN_DAYS]: t('Last 14 days')} : {}), ...(rule.detectionType === AlertRuleComparisonType.DYNAMIC ? {[TimePeriod.TWENTY_EIGHT_DAYS]: t('Last 28 days')} : {}), }; const isSnoozed = rule.snooze; const ruleActionCategory = getAlertRuleActionCategory(rule); const showOnDemandMetricAlertUI = isOnDemandMetricAlert(dataset, aggregate, query) && shouldShowOnDemandMetricAlertUI(organization); const formattedAggregate = aggregate; return ( {selectedIncident?.alertRule.status === AlertRuleStatus.SNAPSHOT && ( {t('Alert Rule settings have been updated since this alert was triggered.')} )} {isSnoozed && ( {ruleActionCategory === RuleActionsCategories.NO_DEFAULT ? tct( "[creator] muted this alert so these notifications won't be sent in the future.", {creator: rule.snoozeCreatedBy} ) : tct( "[creator] muted this alert[forEveryone]so you won't get these notifications in the future.", { creator: rule.snoozeCreatedBy, forEveryone: rule.snoozeForEveryone ? ' for everyone ' : ' ', } )} )} {selectedIncident && ( Remove filter on alert #{selectedIncident.identifier} )} {selectedIncident?.alertRule.detectionType === AlertRuleComparisonType.DYNAMIC && ( )} {[Dataset.METRICS, Dataset.SESSIONS, Dataset.ERRORS].includes(dataset) && ( )} {[Dataset.TRANSACTIONS, Dataset.GENERIC_METRICS].includes(dataset) && ( )} ); } const DetailWrapper = styled('div')` display: flex; flex: 1; @media (max-width: ${p => p.theme.breakpoints.small}) { flex-direction: column-reverse; } `; const StyledLayoutBody = styled(Layout.Body)` flex-grow: 0; padding-bottom: 0 !important; @media (min-width: ${p => p.theme.breakpoints.medium}) { grid-template-columns: auto; } `; const ActivityWrapper = styled('div')` display: flex; flex: 1; flex-direction: column; width: 100%; `; const ChartPanel = styled(Panel)` margin-top: ${space(2)}; `; const StyledSubHeader = styled('div')` margin-bottom: ${space(2)}; display: flex; align-items: center; `; const StyledTimeRangeSelector = styled(TimeRangeSelector)` margin-right: ${space(1)}; `;