metricHistory.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import {Fragment} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import moment from 'moment-timezone';
  5. import CollapsePanel from 'sentry/components/collapsePanel';
  6. import DateTime from 'sentry/components/dateTime';
  7. import Duration from 'sentry/components/duration';
  8. import Link from 'sentry/components/links/link';
  9. import PanelTable from 'sentry/components/panels/panelTable';
  10. import StatusIndicator from 'sentry/components/statusIndicator';
  11. import {t, tn} from 'sentry/locale';
  12. import {space} from 'sentry/styles/space';
  13. import type {Organization} from 'sentry/types';
  14. import getDynamicText from 'sentry/utils/getDynamicText';
  15. import useOrganization from 'sentry/utils/useOrganization';
  16. import {AlertRuleThresholdType} from 'sentry/views/alerts/rules/metric/types';
  17. import type {ActivityType} from 'sentry/views/alerts/types';
  18. import {Incident, IncidentActivityType, IncidentStatus} from 'sentry/views/alerts/types';
  19. import {alertDetailsLink} from 'sentry/views/alerts/utils';
  20. import {AlertWizardAlertNames} from 'sentry/views/alerts/wizard/options';
  21. import {getAlertTypeFromAggregateDataset} from 'sentry/views/alerts/wizard/utils';
  22. const COLLAPSE_COUNT = 3;
  23. type MetricAlertActivityProps = {
  24. incident: Incident;
  25. organization: Organization;
  26. };
  27. function MetricAlertActivity({organization, incident}: MetricAlertActivityProps) {
  28. const activities = (incident.activities ?? []).filter(
  29. activity => activity.type === IncidentActivityType.STATUS_CHANGE
  30. );
  31. const criticalActivity = activities.find(
  32. activity => activity.value === `${IncidentStatus.CRITICAL}`
  33. );
  34. const warningActivity = activities.find(
  35. activity => activity.value === `${IncidentStatus.WARNING}`
  36. );
  37. const triggeredActivity: ActivityType = criticalActivity
  38. ? criticalActivity!
  39. : warningActivity!;
  40. const isCritical = Number(triggeredActivity.value) === IncidentStatus.CRITICAL;
  41. // Find duration by looking at the difference between the previous and current activity timestamp
  42. const nextActivity = activities.find(
  43. ({previousValue}) => previousValue === triggeredActivity.value
  44. );
  45. const activityDuration = (
  46. nextActivity ? moment(nextActivity.dateCreated) : moment()
  47. ).diff(moment(triggeredActivity.dateCreated), 'milliseconds');
  48. const triggerLabel = isCritical ? 'critical' : 'warning';
  49. const curentTrigger = incident.alertRule.triggers.find(
  50. trigger => trigger.label === triggerLabel
  51. );
  52. return (
  53. <Fragment>
  54. <Cell>
  55. {triggeredActivity.value && (
  56. <StatusIndicator
  57. status={isCritical ? 'error' : 'warning'}
  58. tooltipTitle={t('Status: %s', isCritical ? t('Critical') : t('Warning'))}
  59. />
  60. )}
  61. <Link
  62. to={{
  63. pathname: alertDetailsLink(organization, incident),
  64. query: {alert: incident.identifier},
  65. }}
  66. >
  67. #{incident.identifier}
  68. </Link>
  69. </Cell>
  70. <Cell>
  71. {AlertWizardAlertNames[getAlertTypeFromAggregateDataset(incident.alertRule)]}{' '}
  72. {incident.alertRule.thresholdType === AlertRuleThresholdType.ABOVE
  73. ? t('above')
  74. : t('below')}{' '}
  75. {curentTrigger?.alertThreshold}
  76. </Cell>
  77. <Cell>
  78. {activityDuration &&
  79. getDynamicText({
  80. value: <Duration abbreviation seconds={activityDuration / 1000} />,
  81. fixed: '30s',
  82. })}
  83. </Cell>
  84. <Cell>
  85. <StyledDateTime
  86. date={getDynamicText({
  87. value: incident.dateCreated,
  88. fixed: 'Mar 4, 2022 10:44:13 AM UTC',
  89. })}
  90. year
  91. seconds
  92. timeZone
  93. />
  94. </Cell>
  95. </Fragment>
  96. );
  97. }
  98. type Props = {
  99. incidents?: Incident[];
  100. };
  101. function MetricHistory({incidents}: Props) {
  102. const organization = useOrganization();
  103. const filteredIncidents = (incidents ?? []).filter(
  104. incident => incident.activities?.length
  105. );
  106. const numOfIncidents = filteredIncidents.length;
  107. return (
  108. <CollapsePanel
  109. items={numOfIncidents}
  110. collapseCount={COLLAPSE_COUNT}
  111. disableBorder={false}
  112. buttonTitle={tn('Hidden Alert', 'Hidden Alerts', numOfIncidents - COLLAPSE_COUNT)}
  113. >
  114. {({isExpanded, showMoreButton}) => (
  115. <div>
  116. <StyledPanelTable
  117. headers={[t('Alert'), t('Reason'), t('Duration'), t('Date Triggered')]}
  118. isEmpty={!numOfIncidents}
  119. emptyMessage={t('No alerts triggered during this time.')}
  120. expanded={numOfIncidents <= COLLAPSE_COUNT || isExpanded}
  121. >
  122. {filteredIncidents.map((incident, idx) => {
  123. if (idx >= COLLAPSE_COUNT && !isExpanded) {
  124. return null;
  125. }
  126. return (
  127. <MetricAlertActivity
  128. key={idx}
  129. incident={incident}
  130. organization={organization}
  131. />
  132. );
  133. })}
  134. </StyledPanelTable>
  135. {showMoreButton}
  136. </div>
  137. )}
  138. </CollapsePanel>
  139. );
  140. }
  141. export default MetricHistory;
  142. const StyledPanelTable = styled(PanelTable)<{expanded: boolean; isEmpty: boolean}>`
  143. grid-template-columns: max-content 1fr repeat(2, max-content);
  144. & > div {
  145. padding: ${space(1)} ${space(2)};
  146. }
  147. div:last-of-type {
  148. padding: ${p => p.isEmpty && `48px ${space(1)}`};
  149. }
  150. ${p =>
  151. !p.expanded &&
  152. css`
  153. margin-bottom: 0px;
  154. border-bottom-left-radius: 0px;
  155. border-bottom-right-radius: 0px;
  156. border-bottom: none;
  157. `}
  158. `;
  159. const StyledDateTime = styled(DateTime)`
  160. color: ${p => p.theme.gray300};
  161. `;
  162. const Cell = styled('div')`
  163. display: flex;
  164. align-items: center;
  165. white-space: nowrap;
  166. font-size: ${p => p.theme.fontSizeMedium};
  167. padding: ${space(1)};
  168. `;