archive.tsx 4.8 KB

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