archive.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import styled from '@emotion/styled';
  2. import {getIgnoreActions} from 'sentry/components/actions/ignore';
  3. import {Button} from 'sentry/components/button';
  4. import ButtonBar from 'sentry/components/buttonBar';
  5. import {openConfirmModal} from 'sentry/components/confirm';
  6. import type {MenuItemProps} from 'sentry/components/dropdownMenu';
  7. import {DropdownMenu} from 'sentry/components/dropdownMenu';
  8. import ExternalLink from 'sentry/components/links/externalLink';
  9. import {IconChevron} from 'sentry/icons';
  10. import {t, tct} from 'sentry/locale';
  11. import type {GroupStatusResolution} from 'sentry/types';
  12. import {GroupStatus, GroupSubstatus} from 'sentry/types';
  13. interface ArchiveActionProps {
  14. onUpdate: (params: GroupStatusResolution) => void;
  15. className?: string;
  16. confirmLabel?: string;
  17. confirmMessage?: () => React.ReactNode;
  18. disableArchiveUntilOccurrence?: boolean;
  19. disabled?: boolean;
  20. isArchived?: boolean;
  21. shouldConfirm?: boolean;
  22. size?: 'xs' | 'sm';
  23. }
  24. const ARCHIVE_UNTIL_ESCALATING: GroupStatusResolution = {
  25. status: GroupStatus.IGNORED,
  26. statusDetails: {},
  27. substatus: GroupSubstatus.ARCHIVED_UNTIL_ESCALATING,
  28. };
  29. const ARCHIVE_FOREVER: GroupStatusResolution = {
  30. status: GroupStatus.IGNORED,
  31. statusDetails: {},
  32. substatus: GroupSubstatus.ARCHIVED_FOREVER,
  33. };
  34. type GetArchiveActionsProps = Pick<
  35. ArchiveActionProps,
  36. 'shouldConfirm' | 'confirmMessage' | 'onUpdate' | 'confirmLabel'
  37. > & {
  38. disableArchiveUntilOccurrence?: boolean;
  39. };
  40. export function getArchiveActions({
  41. shouldConfirm,
  42. confirmLabel,
  43. confirmMessage,
  44. onUpdate,
  45. disableArchiveUntilOccurrence,
  46. }: GetArchiveActionsProps): {
  47. dropdownItems: MenuItemProps[];
  48. onArchive: (resolution: GroupStatusResolution) => void;
  49. } {
  50. // TODO(workflow): Replace ignore actions with more archive actions
  51. const {dropdownItems} = getIgnoreActions({
  52. confirmLabel,
  53. onUpdate,
  54. shouldConfirm,
  55. confirmMessage,
  56. });
  57. const onArchive = (resolution: GroupStatusResolution) => {
  58. if (shouldConfirm && confirmMessage) {
  59. openConfirmModal({
  60. onConfirm: () => onUpdate(resolution),
  61. message: confirmMessage(),
  62. confirmText: confirmLabel,
  63. });
  64. } else {
  65. onUpdate(resolution);
  66. }
  67. };
  68. return {
  69. onArchive,
  70. dropdownItems: [
  71. {
  72. key: 'untilEscalating',
  73. label: t('Until escalating'),
  74. details: t('When events exceed their weekly forecast'),
  75. onAction: () => onArchive(ARCHIVE_UNTIL_ESCALATING),
  76. },
  77. {
  78. key: 'forever',
  79. label: t('Forever'),
  80. onAction: () => onArchive(ARCHIVE_FOREVER),
  81. },
  82. ...dropdownItems.filter(item => {
  83. if (disableArchiveUntilOccurrence) {
  84. return item.key !== 'until-reoccur' && item.key !== 'until-affect';
  85. }
  86. return true;
  87. }),
  88. ],
  89. };
  90. }
  91. function ArchiveActions({
  92. size = 'xs',
  93. disabled,
  94. disableArchiveUntilOccurrence,
  95. className,
  96. shouldConfirm,
  97. confirmLabel,
  98. isArchived,
  99. confirmMessage,
  100. onUpdate,
  101. }: ArchiveActionProps) {
  102. if (isArchived) {
  103. return (
  104. <Button
  105. priority="primary"
  106. size="xs"
  107. title={t('Change status to unresolved')}
  108. onClick={() =>
  109. onUpdate({
  110. status: GroupStatus.UNRESOLVED,
  111. statusDetails: {},
  112. substatus: GroupSubstatus.ONGOING,
  113. })
  114. }
  115. aria-label={t('Unarchive')}
  116. />
  117. );
  118. }
  119. const {dropdownItems, onArchive} = getArchiveActions({
  120. confirmLabel,
  121. onUpdate,
  122. shouldConfirm,
  123. confirmMessage,
  124. disableArchiveUntilOccurrence,
  125. });
  126. return (
  127. <ButtonBar className={className} merged>
  128. <ArchiveButton
  129. size={size}
  130. tooltipProps={{delay: 1000, disabled, isHoverable: true}}
  131. title={tct(
  132. 'We’ll nag you with a notification if the issue gets worse. All archived issues can be found in the Archived tab. [docs:Read the docs]',
  133. {
  134. docs: (
  135. <ExternalLink href="https://docs.sentry.io/product/issues/states-triage/#archive" />
  136. ),
  137. }
  138. )}
  139. onClick={() => onArchive(ARCHIVE_UNTIL_ESCALATING)}
  140. disabled={disabled}
  141. >
  142. {t('Archive')}
  143. </ArchiveButton>
  144. <DropdownMenu
  145. minMenuWidth={270}
  146. size="sm"
  147. trigger={triggerProps => (
  148. <DropdownTrigger
  149. {...triggerProps}
  150. aria-label={t('Archive options')}
  151. size={size}
  152. icon={<IconChevron direction="down" />}
  153. disabled={disabled}
  154. />
  155. )}
  156. menuTitle={
  157. <MenuWrapper>
  158. {t('Archive')}
  159. <StyledExternalLink href="https://docs.sentry.io/product/issues/states-triage/#archive">
  160. {t('Read the docs')}
  161. </StyledExternalLink>
  162. </MenuWrapper>
  163. }
  164. items={dropdownItems}
  165. isDisabled={disabled}
  166. />
  167. </ButtonBar>
  168. );
  169. }
  170. export default ArchiveActions;
  171. const ArchiveButton = styled(Button)`
  172. box-shadow: none;
  173. border-radius: ${p => p.theme.borderRadiusLeft};
  174. `;
  175. const DropdownTrigger = styled(Button)`
  176. box-shadow: none;
  177. border-radius: ${p => p.theme.borderRadiusRight};
  178. border-left: none;
  179. `;
  180. const MenuWrapper = styled('div')`
  181. display: flex;
  182. justify-content: space-between;
  183. align-items: center;
  184. `;
  185. const StyledExternalLink = styled(ExternalLink)`
  186. font-weight: normal;
  187. `;