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)};
`;