import {Fragment} from 'react'; import styled from '@emotion/styled'; import AlertBadge from 'sentry/components/alertBadge'; import {OnDemandWarningIcon} from 'sentry/components/alerts/onDemandMetricAlert'; import ActorAvatar from 'sentry/components/avatar/actorAvatar'; import {SectionHeading} from 'sentry/components/charts/styles'; import DateTime from 'sentry/components/dateTime'; import Duration from 'sentry/components/duration'; import {KeyValueTable, KeyValueTableRow} from 'sentry/components/keyValueTable'; import TimeSince from 'sentry/components/timeSince'; import {IconDiamond} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Actor} from 'sentry/types'; import getDynamicText from 'sentry/utils/getDynamicText'; import {getSearchFilters, isOnDemandSearchKey} from 'sentry/utils/onDemandMetrics/index'; import {capitalize} from 'sentry/utils/string/capitalize'; import {COMPARISON_DELTA_OPTIONS} from 'sentry/views/alerts/rules/metric/constants'; import type {Action, MetricRule} from 'sentry/views/alerts/rules/metric/types'; import { AlertRuleThresholdType, AlertRuleTriggerType, } from 'sentry/views/alerts/rules/metric/types'; import {IncidentStatus} from 'sentry/views/alerts/types'; import {AlertWizardAlertNames} from 'sentry/views/alerts/wizard/options'; import {getAlertTypeFromAggregateDataset} from 'sentry/views/alerts/wizard/utils'; interface MetricDetailsSidebarProps { rule: MetricRule; showOnDemandMetricAlertUI: boolean; } function TriggerDescription({ rule, actions, label, threshold, }: { actions: Action[]; label: string; rule: MetricRule; threshold: number; }) { const status = label === AlertRuleTriggerType.CRITICAL ? t('Critical') : label === AlertRuleTriggerType.WARNING ? t('Warning') : t('Resolved'); const statusIconColor = label === AlertRuleTriggerType.CRITICAL ? 'errorText' : label === AlertRuleTriggerType.WARNING ? 'warningText' : 'successText'; const defaultAction = t('Change alert status to %s', status); const aboveThreshold = label === AlertRuleTriggerType.RESOLVE ? rule.thresholdType === AlertRuleThresholdType.BELOW : rule.thresholdType === AlertRuleThresholdType.ABOVE; const thresholdTypeText = aboveThreshold ? rule.comparisonDelta ? t('higher') : t('above') : rule.comparisonDelta ? t('lower') : t('below'); const timeWindow = ; const metricName = capitalize( AlertWizardAlertNames[getAlertTypeFromAggregateDataset(rule)] ); const thresholdText = rule.comparisonDelta ? tct( '[metric] is [threshold]% [comparisonType] in [timeWindow] compared to the [comparisonDelta]', { metric: metricName, threshold, comparisonType: thresholdTypeText, timeWindow, comparisonDelta: ( COMPARISON_DELTA_OPTIONS.find(({value}) => value === rule.comparisonDelta) ?? COMPARISON_DELTA_OPTIONS[0] ).label, } ) : tct('[metric] is [condition] in [timeWindow]', { metric: metricName, condition: `${thresholdTypeText} ${threshold}`, timeWindow, }); return ( {t('%s Conditions', status)} {t('When')} {thresholdText} {t('Then')} {actions.map(action => ( {action.desc} ))} {defaultAction} ); } export function MetricDetailsSidebar({ rule, showOnDemandMetricAlertUI, }: MetricDetailsSidebarProps) { // get current status const latestIncident = rule.latestIncident; const status = latestIncident ? latestIncident.status : IncidentStatus.CLOSED; // The date at which the alert was triggered or resolved const activityDate = latestIncident?.dateClosed ?? latestIncident?.dateStarted ?? null; const criticalTrigger = rule.triggers.find( ({label}) => label === AlertRuleTriggerType.CRITICAL ); const warningTrigger = rule.triggers.find( ({label}) => label === AlertRuleTriggerType.WARNING ); const ownerId = rule.owner?.split(':')[1]; const teamActor = ownerId && {type: 'team' as Actor['type'], id: ownerId, name: ''}; return ( {t('Alert Status')} {t('Last Triggered')} {activityDate ? : t('No alerts triggered')} {typeof criticalTrigger?.alertThreshold === 'number' && ( )} {typeof warningTrigger?.alertThreshold === 'number' && ( )} {typeof rule.resolveThreshold === 'number' && ( )} {showOnDemandMetricAlertUI && ( {t('Filters Used')} {getSearchFilters(rule.query).map(({key, operator, value}) => ( ))} )} {t('Alert Rule Details')} {rule.environment ?? '-'}} /> } /> {rule.createdBy && ( {rule.createdBy.name ?? '-'} } /> )} {rule.dateModified && ( } /> )} : t('Unassigned') } /> ); } function FilterKeyValueTableRow({ keyName, operator, value, }: { keyName: string; operator: string; value: string; }) { return ( {isOnDemandSearchKey(keyName) && ( )} {keyName} } value={ {operator} {value} } /> ); } const KeyWrapper = styled('div')` display: flex; gap: ${space(0.75)}; > span { margin-top: ${space(0.25)}; height: ${space(2)}; } `; const SidebarGroup = styled('div')` margin-bottom: ${space(3)}; `; const HeaderItem = styled('div')` flex: 1; display: flex; flex-direction: column; > *:nth-child(2) { flex: 1; display: flex; align-items: center; } `; const Status = styled('div')` position: relative; display: grid; grid-template-columns: auto auto auto; gap: ${space(0.5)}; font-size: ${p => p.theme.fontSizeLarge}; `; const StatusContainer = styled('div')` height: 60px; display: flex; margin-bottom: ${space(1)}; `; const Heading = styled(SectionHeading)<{noMargin?: boolean}>` margin-top: ${p => (p.noMargin ? 0 : space(2))}; margin-bottom: ${p => (p.noMargin ? 0 : space(1))}; `; const OverflowTableValue = styled('div')` ${p => p.theme.overflowEllipsis} `; const TriggerContainer = styled('div')` display: grid; grid-template-rows: auto auto auto; gap: ${space(1)}; margin-top: ${space(4)}; `; const TriggerTitle = styled('div')` display: grid; grid-template-columns: 20px 1fr; align-items: center; `; const TriggerTitleText = styled('h4')` color: ${p => p.theme.subText}; font-size: ${p => p.theme.fontSizeMedium}; margin: 0; line-height: 24px; min-width: 40px; `; const TriggerStep = styled('div')` display: grid; grid-template-columns: 40px 1fr; align-items: stretch; `; const TriggerActions = styled('div')` display: grid; grid-template-columns: repeat(1fr); gap: ${space(0.25)}; align-items: center; `; const TriggerText = styled('span')` display: block; background-color: ${p => p.theme.surface200}; padding: ${space(0.25)} ${space(0.75)}; border-radius: ${p => p.theme.borderRadius}; color: ${p => p.theme.textColor}; font-size: ${p => p.theme.fontSizeSmall}; width: 100%; font-weight: 400; `;