activatedMetricAlertRuleStatus.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import type {ReactNode} from 'react';
  2. import styled from '@emotion/styled';
  3. import {IconArrow, IconMute} from 'sentry/icons';
  4. import {t} from 'sentry/locale';
  5. import {space} from 'sentry/styles/space';
  6. import type {ColorOrAlias} from 'sentry/utils/theme';
  7. import {getThresholdUnits} from 'sentry/views/alerts/rules/metric/constants';
  8. import {
  9. AlertRuleComparisonType,
  10. AlertRuleThresholdType,
  11. AlertRuleTriggerType,
  12. } from 'sentry/views/alerts/rules/metric/types';
  13. import type {MetricAlert} from '../../types';
  14. import {IncidentStatus} from '../../types';
  15. interface Props {
  16. rule: MetricAlert;
  17. }
  18. export default function ActivatedMetricAlertRuleStatus({rule}: Props): ReactNode {
  19. if (rule.snooze) {
  20. return (
  21. <IssueAlertStatusWrapper>
  22. <IconMute size="sm" color="subText" />
  23. {t('Muted')}
  24. </IssueAlertStatusWrapper>
  25. );
  26. }
  27. const isUnhealthy =
  28. rule.latestIncident?.status !== undefined &&
  29. [IncidentStatus.CRITICAL, IncidentStatus.WARNING].includes(
  30. rule.latestIncident.status
  31. );
  32. let iconColor: ColorOrAlias = 'successText';
  33. let iconDirection: 'up' | 'down' =
  34. rule.thresholdType === AlertRuleThresholdType.ABOVE ? 'down' : 'up';
  35. let thresholdTypeText =
  36. rule.thresholdType === AlertRuleThresholdType.ABOVE ? t('Below') : t('Above');
  37. if (isUnhealthy) {
  38. iconColor =
  39. rule.latestIncident?.status === IncidentStatus.CRITICAL
  40. ? 'errorText'
  41. : 'warningText';
  42. // if unhealthy, swap icon direction
  43. iconDirection = rule.thresholdType === AlertRuleThresholdType.ABOVE ? 'up' : 'down';
  44. thresholdTypeText =
  45. rule.thresholdType === AlertRuleThresholdType.ABOVE ? t('Above') : t('Below');
  46. }
  47. let threshold = rule.triggers.find(
  48. ({label}) => label === AlertRuleTriggerType.CRITICAL
  49. )?.alertThreshold;
  50. if (isUnhealthy && rule.latestIncident?.status === IncidentStatus.WARNING) {
  51. threshold = rule.triggers.find(
  52. ({label}) => label === AlertRuleTriggerType.WARNING
  53. )?.alertThreshold;
  54. } else if (!isUnhealthy && rule.latestIncident && rule.resolveThreshold) {
  55. threshold = rule.resolveThreshold;
  56. }
  57. return (
  58. <FlexCenter>
  59. <IconArrow color={iconColor} direction={iconDirection} />
  60. <TriggerText>
  61. {`${thresholdTypeText} ${threshold}`}
  62. {getThresholdUnits(
  63. rule.aggregate,
  64. rule.comparisonDelta
  65. ? AlertRuleComparisonType.CHANGE
  66. : AlertRuleComparisonType.COUNT
  67. )}
  68. </TriggerText>
  69. </FlexCenter>
  70. );
  71. }
  72. // TODO: see static/app/components/profiling/flex.tsx and utilize the FlexContainer styled component
  73. const FlexCenter = styled('div')`
  74. display: flex;
  75. flex-direction: row;
  76. align-items: center;
  77. `;
  78. const IssueAlertStatusWrapper = styled('div')`
  79. display: flex;
  80. align-items: center;
  81. gap: ${space(1)};
  82. line-height: 2;
  83. `;
  84. const TriggerText = styled('div')`
  85. margin-left: ${space(1)};
  86. white-space: nowrap;
  87. font-variant-numeric: tabular-nums;
  88. `;