import {Fragment} from 'react';
import type {RouteComponentProps} from 'react-router';
import styled from '@emotion/styled';
import type {Location} from 'history';
import moment from 'moment';
import type {Client} from 'sentry/api';
import {Alert} from 'sentry/components/alert';
import {getInterval} from 'sentry/components/charts/utils';
import * as Layout from 'sentry/components/layouts/thirds';
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 {t, tct} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {RuleActionsCategories} from 'sentry/types/alerts';
import type {Organization} from 'sentry/types/organization';
import type {Project} from 'sentry/types/project';
import {shouldShowOnDemandMetricAlertUI} from 'sentry/utils/onDemandMetrics/features';
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 {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 {Incident} from 'sentry/views/alerts/types';
import {AlertRuleStatus} from 'sentry/views/alerts/types';
import {isCrashFreeAlert} from '../utils/isCrashFreeAlert';
import {isCustomMetricAlert} from '../utils/isCustomMetricAlert';
import type {TimePeriodType} from './constants';
import {
API_INTERVAL_POINTS_LIMIT,
SELECTOR_RELATIVE_PERIODS,
TIME_WINDOWS,
} from './constants';
import MetricChart from './metricChart';
import RelatedIssues from './relatedIssues';
import RelatedTransactions from './relatedTransactions';
import {MetricDetailsSidebar} from './sidebar';
interface MetricDetailsBodyProps extends RouteComponentProps<{}, {}> {
api: Client;
location: Location;
organization: Organization;
timePeriod: TimePeriodType;
incidents?: Incident[];
project?: Project;
rule?: MetricRule;
selectedIncident?: Incident | null;
}
export default function MetricDetailsBody({
api,
project,
rule,
incidents,
organization,
timePeriod,
selectedIncident,
location,
router,
}: MetricDetailsBodyProps) {
function getPeriodInterval() {
const startDate = moment.utc(timePeriod.start);
const endDate = moment.utc(timePeriod.end);
const timeWindow = rule?.timeWindow;
const startEndDifferenceMs = endDate.diff(startDate);
if (
timeWindow &&
(startEndDifferenceMs < API_INTERVAL_POINTS_LIMIT * timeWindow * 60 * 1000 ||
// Special case 7 days * 1m interval over the api limit
startEndDifferenceMs === TIME_WINDOWS[TimePeriod.SEVEN_DAYS])
) {
return `${timeWindow}m`;
}
return getInterval({start: timePeriod.start, end: timePeriod.end}, 'high');
}
function getFilter(): string[] | null {
if (!rule) {
return null;
}
const {aggregate, dataset, query} = rule;
if (isCrashFreeAlert(dataset) || isCustomMetricAlert(aggregate)) {
return query.trim().split(' ');
}
const eventType = extractEventTypeFilterFromRule(rule);
return (query ? `(${eventType}) AND (${query.trim()})` : eventType).split(' ');
}
const handleTimePeriodChange = (datetime: ChangeData) => {
const {start, end, relative} = datetime;
if (start && end) {
return router.push({
...location,
query: {
start: moment(start).utc().format(),
end: moment(end).utc().format(),
},
});
}
return router.push({
...location,
query: {
period: relative,
},
});
};
if (!rule || !project) {
return (
);
}
const {dataset, aggregate, query} = rule;
const eventType = extractEventTypeFilterFromRule(rule);
const queryWithTypeFilter = (
query ? `(${query}) AND (${eventType})` : eventType
).trim();
const relativeOptions = {
...SELECTOR_RELATIVE_PERIODS,
...(rule.timeWindow > 1 ? {[TimePeriod.FOURTEEN_DAYS]: t('Last 14 days')} : {}),
};
const isSnoozed = rule.snooze;
const ruleActionCategory = getAlertRuleActionCategory(rule);
const showOnDemandMetricAlertUI =
isOnDemandMetricAlert(dataset, aggregate, query) &&
shouldShowOnDemandMetricAlertUI(organization);
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 ' : ' ',
}
)}
)}
{/* TODO: add activation start/stop into chart */}
{[Dataset.METRICS, Dataset.SESSIONS, Dataset.ERRORS].includes(dataset) && (
)}
{dataset === Dataset.TRANSACTIONS && (
)}
);
}
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 StyledAlert = styled(Alert)`
margin: 0;
`;
const ActivityWrapper = styled('div')`
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
`;
const ChartPanel = styled(Panel)`
margin-top: ${space(2)};
`;
const StyledTimeRangeSelector = styled(TimeRangeSelector)`
margin-bottom: ${space(2)};
`;