compactIssue.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import {Component, Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import createReactClass from 'create-react-class';
  4. import Reflux from 'reflux';
  5. import {bulkUpdate} from 'app/actionCreators/group';
  6. import {addLoadingMessage, clearIndicators} from 'app/actionCreators/indicator';
  7. import {Client} from 'app/api';
  8. import EventOrGroupTitle from 'app/components/eventOrGroupTitle';
  9. import ErrorLevel from 'app/components/events/errorLevel';
  10. import Link from 'app/components/links/link';
  11. import {PanelItem} from 'app/components/panels';
  12. import GroupChart from 'app/components/stream/groupChart';
  13. import {IconChat, IconMute, IconStar} from 'app/icons';
  14. import {t} from 'app/locale';
  15. import GroupStore from 'app/stores/groupStore';
  16. import space from 'app/styles/space';
  17. import {Group, LightWeightOrganization} from 'app/types';
  18. import {getMessage} from 'app/utils/events';
  19. import {Aliases} from 'app/utils/theme';
  20. import withApi from 'app/utils/withApi';
  21. import withOrganization from 'app/utils/withOrganization';
  22. type HeaderProps = {
  23. organization: LightWeightOrganization;
  24. projectId: string;
  25. data: Group;
  26. eventId?: string;
  27. };
  28. class CompactIssueHeader extends Component<HeaderProps> {
  29. render() {
  30. const {data, organization, projectId, eventId} = this.props;
  31. const basePath = `/organizations/${organization.slug}/issues/`;
  32. const issueLink = eventId
  33. ? `/organizations/${organization.slug}/projects/${projectId}/events/${eventId}/`
  34. : `${basePath}${data.id}/`;
  35. const commentColor: keyof Aliases =
  36. data.subscriptionDetails && data.subscriptionDetails.reason === 'mentioned'
  37. ? 'success'
  38. : 'textColor';
  39. return (
  40. <Fragment>
  41. <IssueHeaderMetaWrapper>
  42. <StyledErrorLevel size="12px" level={data.level} title={data.level} />
  43. <h3 className="truncate">
  44. <IconLink to={issueLink || ''}>
  45. {data.status === 'ignored' && <IconMute size="xs" />}
  46. {data.isBookmarked && <IconStar isSolid size="xs" />}
  47. <EventOrGroupTitle data={data} />
  48. </IconLink>
  49. </h3>
  50. </IssueHeaderMetaWrapper>
  51. <div className="event-extra">
  52. <span className="project-name">
  53. <strong>{data.project.slug}</strong>
  54. </span>
  55. {data.numComments !== 0 && (
  56. <span>
  57. <IconLink to={`${basePath}${data.id}/activity/`} className="comments">
  58. <IconChat size="xs" color={commentColor} />
  59. <span className="tag-count">{data.numComments}</span>
  60. </IconLink>
  61. </span>
  62. )}
  63. <span className="culprit">{getMessage(data)}</span>
  64. </div>
  65. </Fragment>
  66. );
  67. }
  68. }
  69. type Props = {
  70. api: Client;
  71. id: string;
  72. organization: LightWeightOrganization;
  73. statsPeriod?: string;
  74. eventId?: string;
  75. data?: Group;
  76. };
  77. type State = {
  78. issue: Group;
  79. };
  80. const CompactIssue = createReactClass<Props, State>({
  81. displayName: 'CompactIssue',
  82. mixins: [Reflux.listenTo(GroupStore, 'onGroupChange') as any],
  83. getInitialState() {
  84. return {
  85. issue: this.props.data || GroupStore.get(this.props.id),
  86. };
  87. },
  88. componentWillReceiveProps(nextProps: Props) {
  89. if (nextProps.id !== this.props.id) {
  90. this.setState({
  91. issue: GroupStore.get(this.props.id),
  92. });
  93. }
  94. },
  95. onGroupChange(itemIds: Set<string>) {
  96. if (!itemIds.has(this.props.id)) {
  97. return;
  98. }
  99. const id = this.props.id;
  100. const issue = GroupStore.get(id);
  101. this.setState({
  102. issue,
  103. });
  104. },
  105. onSnooze(duration) {
  106. const data: Record<string, string> = {
  107. status: 'ignored',
  108. };
  109. if (duration) {
  110. data.ignoreDuration = duration;
  111. }
  112. this.onUpdate(data);
  113. },
  114. onUpdate(data: Record<string, string>) {
  115. const issue = this.state.issue;
  116. addLoadingMessage(t('Saving changes\u2026'));
  117. bulkUpdate(
  118. this.props.api,
  119. {
  120. orgId: this.props.organization.slug,
  121. projectId: issue.project.slug,
  122. itemIds: [issue.id],
  123. data,
  124. },
  125. {
  126. complete: () => {
  127. clearIndicators();
  128. },
  129. }
  130. );
  131. },
  132. render() {
  133. const issue = this.state.issue;
  134. const {organization} = this.props;
  135. let className = 'issue';
  136. if (issue.isBookmarked) {
  137. className += ' isBookmarked';
  138. }
  139. if (issue.hasSeen) {
  140. className += ' hasSeen';
  141. }
  142. if (issue.status === 'resolved') {
  143. className += ' isResolved';
  144. }
  145. if (issue.status === 'ignored') {
  146. className += ' isIgnored';
  147. }
  148. if (this.props.statsPeriod) {
  149. className += ' with-graph';
  150. }
  151. return (
  152. <IssueRow className={className} onClick={this.toggleSelect}>
  153. <CompactIssueHeader
  154. data={issue}
  155. organization={organization}
  156. projectId={issue.project.slug}
  157. eventId={this.props.eventId}
  158. />
  159. {this.props.statsPeriod && (
  160. <div className="event-graph">
  161. <GroupChart statsPeriod={this.props.statsPeriod} data={this.props.data} />
  162. </div>
  163. )}
  164. {this.props.children}
  165. </IssueRow>
  166. );
  167. },
  168. });
  169. export {CompactIssue};
  170. export default withApi(withOrganization(CompactIssue));
  171. const IssueHeaderMetaWrapper = styled('div')`
  172. display: flex;
  173. align-items: center;
  174. `;
  175. const StyledErrorLevel = styled(ErrorLevel)`
  176. display: block;
  177. margin-right: ${space(1)};
  178. `;
  179. const IconLink = styled(Link)`
  180. & > svg {
  181. margin-right: ${space(0.5)};
  182. }
  183. `;
  184. const IssueRow = styled(PanelItem)`
  185. padding-top: ${space(1.5)};
  186. padding-bottom: ${space(0.75)};
  187. flex-direction: column;
  188. `;