groupActivityItem.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import * as React from 'react';
  2. import CommitLink from 'app/components/commitLink';
  3. import Duration from 'app/components/duration';
  4. import ExternalLink from 'app/components/links/externalLink';
  5. import Link from 'app/components/links/link';
  6. import PullRequestLink from 'app/components/pullRequestLink';
  7. import Version from 'app/components/version';
  8. import {t, tct, tn} from 'app/locale';
  9. import MemberListStore from 'app/stores/memberListStore';
  10. import TeamStore from 'app/stores/teamStore';
  11. import {
  12. GroupActivity,
  13. GroupActivityAssigned,
  14. GroupActivitySetIgnored,
  15. GroupActivityType,
  16. Organization,
  17. Project,
  18. User,
  19. } from 'app/types';
  20. type Props = {
  21. author: React.ReactNode;
  22. activity: GroupActivity;
  23. orgSlug: Organization['slug'];
  24. projectId: Project['id'];
  25. };
  26. function GroupActivityItem({activity, orgSlug, projectId, author}: Props) {
  27. const issuesLink = `/organizations/${orgSlug}/issues/`;
  28. function getIgnoredMessage(data: GroupActivitySetIgnored['data']) {
  29. if (data.ignoreDuration) {
  30. return tct('[author] ignored this issue for [duration]', {
  31. author,
  32. duration: <Duration seconds={data.ignoreDuration * 60} />,
  33. });
  34. }
  35. if (data.ignoreCount && data.ignoreWindow) {
  36. return tct(
  37. '[author] ignored this issue until it happens [count] time(s) in [duration]',
  38. {
  39. author,
  40. count: data.ignoreCount,
  41. duration: <Duration seconds={data.ignoreWindow * 60} />,
  42. }
  43. );
  44. }
  45. if (data.ignoreCount) {
  46. return tct('[author] ignored this issue until it happens [count] time(s)', {
  47. author,
  48. count: data.ignoreCount,
  49. });
  50. }
  51. if (data.ignoreUserCount && data.ignoreUserWindow) {
  52. return tct(
  53. '[author] ignored this issue until it affects [count] user(s) in [duration]',
  54. {
  55. author,
  56. count: data.ignoreUserCount,
  57. duration: <Duration seconds={data.ignoreUserWindow * 60} />,
  58. }
  59. );
  60. }
  61. if (data.ignoreUserCount) {
  62. return tct('[author] ignored this issue until it affects [count] user(s)', {
  63. author,
  64. count: data.ignoreUserCount,
  65. });
  66. }
  67. return tct('[author] ignored this issue', {author});
  68. }
  69. function getAssignedMessage(data: GroupActivityAssigned['data']) {
  70. let assignee: string | User | undefined = undefined;
  71. if (data.assigneeType === 'team') {
  72. const team = TeamStore.getById(data.assignee);
  73. assignee = team ? team.slug : '<unknown-team>';
  74. return tct('[author] assigned this issue to #[assignee]', {
  75. author,
  76. assignee,
  77. });
  78. }
  79. if (activity.user && activity.assignee === activity.user.id) {
  80. return tct('[author] assigned this issue to themselves', {author});
  81. }
  82. assignee = MemberListStore.getById(data.assignee);
  83. if (typeof assignee === 'object' && assignee?.email) {
  84. return tct('[author] assigned this issue to [assignee]', {
  85. author,
  86. assignee: assignee.email,
  87. });
  88. }
  89. return tct('[author] assigned this issue to an unknown user', {author});
  90. }
  91. function renderContent() {
  92. switch (activity.type) {
  93. case GroupActivityType.NOTE:
  94. return tct('[author] left a comment', {author});
  95. case GroupActivityType.SET_RESOLVED:
  96. return tct('[author] marked this issue as resolved', {author});
  97. case GroupActivityType.SET_RESOLVED_BY_AGE:
  98. return tct('[author] marked this issue as resolved due to inactivity', {
  99. author,
  100. });
  101. case GroupActivityType.SET_RESOLVED_IN_RELEASE:
  102. return activity.data.version
  103. ? tct('[author] marked this issue as resolved in [version]', {
  104. author,
  105. version: (
  106. <Version
  107. version={activity.data.version}
  108. projectId={projectId}
  109. tooltipRawVersion
  110. />
  111. ),
  112. })
  113. : tct('[author] marked this issue as resolved in the upcoming release', {
  114. author,
  115. });
  116. case GroupActivityType.SET_RESOLVED_IN_COMMIT:
  117. return tct('[author] marked this issue as resolved in [version]', {
  118. author,
  119. version: (
  120. <CommitLink
  121. inline
  122. commitId={activity.data.commit.id}
  123. repository={activity.data.commit.repository}
  124. />
  125. ),
  126. });
  127. case GroupActivityType.SET_RESOLVED_IN_PULL_REQUEST: {
  128. const {data} = activity;
  129. const {pullRequest} = data;
  130. return tct('[author] marked this issue as resolved in [version]', {
  131. author,
  132. version: (
  133. <PullRequestLink
  134. inline
  135. pullRequest={pullRequest}
  136. repository={pullRequest.repository}
  137. />
  138. ),
  139. });
  140. }
  141. case GroupActivityType.SET_UNRESOLVED:
  142. return tct('[author] marked this issue as unresolved', {author});
  143. case GroupActivityType.SET_IGNORED: {
  144. const {data} = activity;
  145. return getIgnoredMessage(data);
  146. }
  147. case GroupActivityType.SET_PUBLIC:
  148. return tct('[author] made this issue public', {author});
  149. case GroupActivityType.SET_PRIVATE:
  150. return tct('[author] made this issue private', {author});
  151. case GroupActivityType.SET_REGRESSION: {
  152. const {data} = activity;
  153. return data.version
  154. ? tct('[author] marked this issue as a regression in [version]', {
  155. author,
  156. version: (
  157. <Version version={data.version} projectId={projectId} tooltipRawVersion />
  158. ),
  159. })
  160. : tct('[author] marked this issue as a regression', {author});
  161. }
  162. case GroupActivityType.CREATE_ISSUE: {
  163. const {data} = activity;
  164. return tct('[author] created an issue on [provider] titled [title]', {
  165. author,
  166. provider: data.provider,
  167. title: <ExternalLink href={data.location}>{data.title}</ExternalLink>,
  168. });
  169. }
  170. case GroupActivityType.UNMERGE_SOURCE: {
  171. const {data} = activity;
  172. const {destination, fingerprints} = data;
  173. return tn(
  174. '%2$s migrated %1$s fingerprint to %3$s',
  175. '%2$s migrated %1$s fingerprints to %3$s',
  176. fingerprints.length,
  177. author,
  178. destination ? (
  179. <Link to={`${issuesLink}${destination.id}`}>{destination.shortId}</Link>
  180. ) : (
  181. t('a group')
  182. )
  183. );
  184. }
  185. case GroupActivityType.UNMERGE_DESTINATION: {
  186. const {data} = activity;
  187. const {source, fingerprints} = data;
  188. return tn(
  189. '%2$s migrated %1$s fingerprint from %3$s',
  190. '%2$s migrated %1$s fingerprints from %3$s',
  191. fingerprints.length,
  192. author,
  193. source ? (
  194. <Link to={`${issuesLink}${source.id}`}>{source.shortId}</Link>
  195. ) : (
  196. t('a group')
  197. )
  198. );
  199. }
  200. case GroupActivityType.FIRST_SEEN:
  201. return tct('[author] first saw this issue', {author});
  202. case GroupActivityType.ASSIGNED: {
  203. const {data} = activity;
  204. return getAssignedMessage(data);
  205. }
  206. case GroupActivityType.UNASSIGNED:
  207. return tct('[author] unassigned this issue', {author});
  208. case GroupActivityType.MERGE:
  209. return tn(
  210. '%2$s merged %1$s issue into this issue',
  211. '%2$s merged %1$s issues into this issue',
  212. activity.data.issues.length,
  213. author
  214. );
  215. case GroupActivityType.REPROCESS: {
  216. const {data} = activity;
  217. const {oldGroupId, eventCount} = data;
  218. return tct('[author] reprocessed the events in this issue. [new-events]', {
  219. author,
  220. ['new-events']: (
  221. <Link
  222. to={`/organizations/${orgSlug}/issues/?query=reprocessing.original_issue_id:${oldGroupId}`}
  223. >
  224. {tn('See %s new event', 'See %s new events', eventCount)}
  225. </Link>
  226. ),
  227. });
  228. }
  229. case GroupActivityType.MARK_REVIEWED: {
  230. return tct('[author] marked this issue as reviewed', {
  231. author,
  232. });
  233. }
  234. default:
  235. return ''; // should never hit (?)
  236. }
  237. }
  238. return <React.Fragment>{renderContent()}</React.Fragment>;
  239. }
  240. export default GroupActivityItem;