groupActivity.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import {Fragment, useCallback, useMemo} from 'react';
  2. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  3. import type {
  4. TContext,
  5. TData,
  6. TError,
  7. TVariables,
  8. } from 'sentry/components/feedback/useMutateActivity';
  9. import useMutateActivity from 'sentry/components/feedback/useMutateActivity';
  10. import * as Layout from 'sentry/components/layouts/thirds';
  11. import LoadingError from 'sentry/components/loadingError';
  12. import LoadingIndicator from 'sentry/components/loadingIndicator';
  13. import ReprocessedBox from 'sentry/components/reprocessedBox';
  14. import {t} from 'sentry/locale';
  15. import GroupStore from 'sentry/stores/groupStore';
  16. import type {NoteType} from 'sentry/types/alerts';
  17. import type {
  18. Group,
  19. GroupActivity as GroupActivityType,
  20. GroupActivityNote,
  21. GroupActivityReprocess,
  22. } from 'sentry/types/group';
  23. import type {User} from 'sentry/types/user';
  24. import type {MutateOptions} from 'sentry/utils/queryClient';
  25. import useOrganization from 'sentry/utils/useOrganization';
  26. import {useParams} from 'sentry/utils/useParams';
  27. import ActivitySection from 'sentry/views/issueDetails/activitySection';
  28. import GroupEventDetails from 'sentry/views/issueDetails/groupEventDetails/groupEventDetails';
  29. import {useGroup} from 'sentry/views/issueDetails/useGroup';
  30. import {
  31. getGroupMostRecentActivity,
  32. getGroupReprocessingStatus,
  33. ReprocessingStatus,
  34. useHasStreamlinedUI,
  35. } from 'sentry/views/issueDetails/utils';
  36. export type MutateActivityOptions = MutateOptions<TData, TError, TVariables, TContext>;
  37. interface GroupActivityProps {
  38. group: Group;
  39. }
  40. export function GroupActivity({group}: GroupActivityProps) {
  41. const organization = useOrganization();
  42. const {activity: activities, count, id: groupId} = group;
  43. const groupCount = Number(count);
  44. const mostRecentActivity = getGroupMostRecentActivity(activities);
  45. const reprocessingStatus = getGroupReprocessingStatus(group, mostRecentActivity);
  46. const mutators = useMutateActivity({
  47. organization,
  48. group,
  49. });
  50. const deleteOptions: MutateActivityOptions = useMemo(() => {
  51. return {
  52. onError: () => {
  53. addErrorMessage(t('Failed to delete comment'));
  54. },
  55. onSuccess: () => {
  56. addSuccessMessage(t('Comment removed'));
  57. },
  58. };
  59. }, []);
  60. const createOptions: MutateActivityOptions = useMemo(() => {
  61. return {
  62. onError: () => {
  63. addErrorMessage(t('Unable to post comment'));
  64. },
  65. onSuccess: data => {
  66. GroupStore.addActivity(group.id, data);
  67. addSuccessMessage(t('Comment posted'));
  68. },
  69. };
  70. }, [group.id]);
  71. const updateOptions: MutateActivityOptions = useMemo(() => {
  72. return {
  73. onError: () => {
  74. addErrorMessage(t('Unable to update comment'));
  75. },
  76. onSuccess: data => {
  77. const d = data as GroupActivityNote;
  78. GroupStore.updateActivity(group.id, data.id, {text: d.data.text});
  79. addSuccessMessage(t('Comment updated'));
  80. },
  81. };
  82. }, [group.id]);
  83. const handleDelete = useCallback(
  84. (item: GroupActivityType) => {
  85. const restore = group.activity.find(activity => activity.id === item.id);
  86. const index = GroupStore.removeActivity(group.id, item.id);
  87. if (index === -1 || restore === undefined) {
  88. addErrorMessage(t('Failed to delete comment'));
  89. return;
  90. }
  91. mutators.handleDelete(
  92. item.id,
  93. group.activity.filter(a => a.id !== item.id),
  94. deleteOptions
  95. );
  96. },
  97. [deleteOptions, group.activity, mutators, group.id]
  98. );
  99. const handleCreate = useCallback(
  100. (n: NoteType, _me: User) => {
  101. mutators.handleCreate(n, group.activity, createOptions);
  102. },
  103. [createOptions, group.activity, mutators]
  104. );
  105. const handleUpdate = useCallback(
  106. (item: GroupActivityType, n: NoteType) => {
  107. mutators.handleUpdate(n, item.id, group.activity, updateOptions);
  108. },
  109. [updateOptions, group.activity, mutators]
  110. );
  111. return (
  112. <Fragment>
  113. {(reprocessingStatus === ReprocessingStatus.REPROCESSED_AND_HASNT_EVENT ||
  114. reprocessingStatus === ReprocessingStatus.REPROCESSED_AND_HAS_EVENT) && (
  115. <Layout.Main fullWidth>
  116. <ReprocessedBox
  117. reprocessActivity={mostRecentActivity as GroupActivityReprocess}
  118. groupCount={groupCount}
  119. orgSlug={organization.slug}
  120. groupId={groupId}
  121. />
  122. </Layout.Main>
  123. )}
  124. <Layout.Main>
  125. <ActivitySection
  126. group={group}
  127. onDelete={handleDelete}
  128. onCreate={handleCreate}
  129. onUpdate={handleUpdate}
  130. placeholderText={t(
  131. 'Add details or updates to this event. \nTag users with @, or teams with #'
  132. )}
  133. />
  134. </Layout.Main>
  135. </Fragment>
  136. );
  137. }
  138. function GroupActivityRoute() {
  139. const hasStreamlinedUI = useHasStreamlinedUI();
  140. const params = useParams<{groupId: string}>();
  141. const {
  142. data: group,
  143. isPending: isGroupPending,
  144. isError: isGroupError,
  145. refetch: refetchGroup,
  146. } = useGroup({groupId: params.groupId});
  147. if (hasStreamlinedUI) {
  148. return <GroupEventDetails />;
  149. }
  150. if (isGroupPending) {
  151. return <LoadingIndicator />;
  152. }
  153. if (isGroupError) {
  154. return <LoadingError onRetry={refetchGroup} />;
  155. }
  156. return (
  157. <Layout.Body>
  158. <GroupActivity group={group} />
  159. </Layout.Body>
  160. );
  161. }
  162. export default GroupActivityRoute;