index.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import {Component, Fragment} from 'react';
  2. import {browserHistory, RouteComponentProps} from 'react-router';
  3. import {Location} from 'history';
  4. import {markIncidentAsSeen} from 'app/actionCreators/incident';
  5. import {addErrorMessage} from 'app/actionCreators/indicator';
  6. import {fetchOrgMembers} from 'app/actionCreators/members';
  7. import {Client} from 'app/api';
  8. import SentryDocumentTitle from 'app/components/sentryDocumentTitle';
  9. import {t} from 'app/locale';
  10. import {Organization} from 'app/types';
  11. import {trackAnalyticsEvent} from 'app/utils/analytics';
  12. import withApi from 'app/utils/withApi';
  13. import {AlertRuleStatus, Incident, IncidentStats, IncidentStatus} from '../types';
  14. import {
  15. fetchIncident,
  16. fetchIncidentStats,
  17. isOpen,
  18. updateStatus,
  19. updateSubscription,
  20. } from '../utils';
  21. import DetailsBody from './body';
  22. import DetailsHeader from './header';
  23. type Props = {
  24. api: Client;
  25. location: Location;
  26. organization: Organization;
  27. } & RouteComponentProps<{alertId: string; orgId: string}, {}>;
  28. type State = {
  29. isLoading: boolean;
  30. hasError: boolean;
  31. incident?: Incident;
  32. stats?: IncidentStats;
  33. };
  34. export const alertDetailsLink = (organization: Organization, incident: Incident) =>
  35. `/organizations/${organization.slug}/alerts/rules/details/${
  36. incident?.alertRule.status === AlertRuleStatus.SNAPSHOT &&
  37. incident?.alertRule.originalAlertRuleId
  38. ? incident?.alertRule.originalAlertRuleId
  39. : incident?.alertRule.id
  40. }/`;
  41. class IncidentDetails extends Component<Props, State> {
  42. state: State = {isLoading: false, hasError: false};
  43. componentDidMount() {
  44. const {api, organization, params} = this.props;
  45. trackAnalyticsEvent({
  46. eventKey: 'alert_details.viewed',
  47. eventName: 'Alert Details: Viewed',
  48. organization_id: parseInt(organization.id, 10),
  49. alert_id: parseInt(params.alertId, 10),
  50. });
  51. fetchOrgMembers(api, params.orgId);
  52. this.fetchData();
  53. }
  54. fetchData = async () => {
  55. this.setState({isLoading: true, hasError: false});
  56. const {
  57. api,
  58. location,
  59. organization,
  60. params: {orgId, alertId},
  61. } = this.props;
  62. try {
  63. const incidentPromise = fetchIncident(api, orgId, alertId).then(incident => {
  64. const hasRedesign =
  65. incident.alertRule &&
  66. this.props.organization.features.includes('alert-details-redesign');
  67. // only stop redirect if param is explicitly set to false
  68. const stopRedirect =
  69. location && location.query && location.query.redirect === 'false';
  70. if (hasRedesign && !stopRedirect) {
  71. browserHistory.replace({
  72. pathname: alertDetailsLink(organization, incident),
  73. query: {alert: incident.identifier},
  74. });
  75. }
  76. this.setState({incident});
  77. markIncidentAsSeen(api, orgId, incident);
  78. });
  79. const statsPromise = fetchIncidentStats(api, orgId, alertId).then(stats =>
  80. this.setState({stats})
  81. );
  82. // State not set after promise.all because stats *usually* takes
  83. // more time than the incident api
  84. await Promise.all([incidentPromise, statsPromise]);
  85. this.setState({isLoading: false, hasError: false});
  86. } catch (_err) {
  87. this.setState({isLoading: false, hasError: true});
  88. }
  89. };
  90. handleSubscriptionChange = async () => {
  91. const {
  92. api,
  93. params: {orgId, alertId},
  94. } = this.props;
  95. if (!this.state.incident) {
  96. return;
  97. }
  98. const isSubscribed = this.state.incident.isSubscribed;
  99. const newIsSubscribed = !isSubscribed;
  100. this.setState(state => ({
  101. incident: {...(state.incident as Incident), isSubscribed: newIsSubscribed},
  102. }));
  103. try {
  104. updateSubscription(api, orgId, alertId, newIsSubscribed);
  105. } catch (_err) {
  106. this.setState(state => ({
  107. incident: {...(state.incident as Incident), isSubscribed},
  108. }));
  109. addErrorMessage(t('An error occurred, your subscription status was not changed.'));
  110. }
  111. };
  112. handleStatusChange = async () => {
  113. const {
  114. api,
  115. params: {orgId, alertId},
  116. } = this.props;
  117. if (!this.state.incident) {
  118. return;
  119. }
  120. const {status} = this.state.incident;
  121. const newStatus = isOpen(this.state.incident) ? IncidentStatus.CLOSED : status;
  122. this.setState(state => ({
  123. incident: {...(state.incident as Incident), status: newStatus},
  124. }));
  125. try {
  126. const incident = await updateStatus(api, orgId, alertId, newStatus);
  127. // Update entire incident object because updating status can cause other parts
  128. // of the model to change (e.g close date)
  129. this.setState({incident});
  130. } catch (_err) {
  131. this.setState(state => ({
  132. incident: {...(state.incident as Incident), status},
  133. }));
  134. addErrorMessage(t('An error occurred, your incident status was not changed.'));
  135. }
  136. };
  137. render() {
  138. const {incident, stats, hasError} = this.state;
  139. const {params, organization} = this.props;
  140. const {alertId} = params;
  141. const project = incident?.projects?.[0];
  142. return (
  143. <Fragment>
  144. <SentryDocumentTitle
  145. title={t('Alert %s', alertId)}
  146. orgSlug={organization.slug}
  147. projectSlug={project}
  148. />
  149. <DetailsHeader
  150. hasIncidentDetailsError={hasError}
  151. params={params}
  152. incident={incident}
  153. stats={stats}
  154. onSubscriptionChange={this.handleSubscriptionChange}
  155. onStatusChange={this.handleStatusChange}
  156. />
  157. <DetailsBody {...this.props} incident={incident} stats={stats} />
  158. </Fragment>
  159. );
  160. }
  161. }
  162. export default withApi(IncidentDetails);