actions.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import Access from 'sentry/components/acl/access';
  4. import {useRole} from 'sentry/components/acl/useRole';
  5. import MenuItemActionLink from 'sentry/components/actions/menuItemActionLink';
  6. import {Button, LinkButton} from 'sentry/components/button';
  7. import ButtonBar from 'sentry/components/buttonBar';
  8. import Confirm from 'sentry/components/confirm';
  9. import DropdownLink from 'sentry/components/dropdownLink';
  10. import {Tooltip} from 'sentry/components/tooltip';
  11. import {IconDelete, IconDownload, IconEllipsis} from 'sentry/icons';
  12. import {t} from 'sentry/locale';
  13. import type {ImageCandidate} from 'sentry/types/debugImage';
  14. import {CandidateDownloadStatus} from 'sentry/types/debugImage';
  15. import type {Organization} from 'sentry/types/organization';
  16. import type {Project} from 'sentry/types/project';
  17. const noPermissionToDownloadDebugFilesInfo = t(
  18. 'You do not have permission to download debug files'
  19. );
  20. const noPermissionToDeleteDebugFilesInfo = t(
  21. 'You do not have permission to delete debug files'
  22. );
  23. const debugFileDeleteConfirmationInfo = t('Are you sure you wish to delete this file?');
  24. type Props = {
  25. baseUrl: string;
  26. candidate: ImageCandidate;
  27. isInternalSource: boolean;
  28. onDelete: (debugFileId: string) => void;
  29. organization: Organization;
  30. projSlug: Project['slug'];
  31. };
  32. function Actions({
  33. candidate,
  34. organization,
  35. isInternalSource,
  36. baseUrl,
  37. projSlug,
  38. onDelete,
  39. }: Props) {
  40. const {download, location: debugFileId} = candidate;
  41. const {status} = download;
  42. const {hasRole} = useRole({role: 'debugFilesRole'});
  43. if (!debugFileId || !isInternalSource) {
  44. return null;
  45. }
  46. const deleted = status === CandidateDownloadStatus.DELETED;
  47. const downloadUrl = `${baseUrl}/projects/${organization.slug}/${projSlug}/files/dsyms/?id=${debugFileId}`;
  48. const actions = (
  49. <Access access={['project:write']}>
  50. {({hasAccess}) => (
  51. <Fragment>
  52. <StyledDropdownLink
  53. caret={false}
  54. customTitle={
  55. <Button
  56. size="xs"
  57. aria-label={t('Actions')}
  58. disabled={deleted}
  59. icon={<IconEllipsis />}
  60. />
  61. }
  62. anchorRight
  63. >
  64. <Tooltip disabled={hasRole} title={noPermissionToDownloadDebugFilesInfo}>
  65. <MenuItemActionLink
  66. shouldConfirm={false}
  67. icon={<IconDownload size="xs" />}
  68. href={downloadUrl}
  69. onClick={event => {
  70. if (deleted) {
  71. event.preventDefault();
  72. }
  73. }}
  74. disabled={!hasRole || deleted}
  75. >
  76. {t('Download')}
  77. </MenuItemActionLink>
  78. </Tooltip>
  79. <Tooltip disabled={hasAccess} title={noPermissionToDeleteDebugFilesInfo}>
  80. <MenuItemActionLink
  81. onAction={() => onDelete(debugFileId)}
  82. message={debugFileDeleteConfirmationInfo}
  83. disabled={!hasAccess || deleted}
  84. shouldConfirm
  85. >
  86. {t('Delete')}
  87. </MenuItemActionLink>
  88. </Tooltip>
  89. </StyledDropdownLink>
  90. <StyledButtonBar gap={1}>
  91. <Tooltip disabled={hasRole} title={noPermissionToDownloadDebugFilesInfo}>
  92. <LinkButton
  93. size="xs"
  94. icon={<IconDownload />}
  95. href={downloadUrl}
  96. disabled={!hasRole}
  97. >
  98. {t('Download')}
  99. </LinkButton>
  100. </Tooltip>
  101. <Tooltip disabled={hasAccess} title={noPermissionToDeleteDebugFilesInfo}>
  102. <Confirm
  103. confirmText={t('Delete')}
  104. message={debugFileDeleteConfirmationInfo}
  105. onConfirm={() => onDelete(debugFileId)}
  106. disabled={!hasAccess}
  107. >
  108. <Button
  109. priority="danger"
  110. icon={<IconDelete />}
  111. size="xs"
  112. disabled={!hasAccess}
  113. aria-label={t('Delete')}
  114. />
  115. </Confirm>
  116. </Tooltip>
  117. </StyledButtonBar>
  118. </Fragment>
  119. )}
  120. </Access>
  121. );
  122. if (!deleted) {
  123. return actions;
  124. }
  125. return (
  126. <Tooltip title={t('Actions not available because this debug file was deleted')}>
  127. {actions}
  128. </Tooltip>
  129. );
  130. }
  131. export default Actions;
  132. const StyledDropdownLink = styled(DropdownLink)`
  133. display: none;
  134. @media (min-width: ${props => props.theme.breakpoints.xxlarge}) {
  135. display: flex;
  136. align-items: center;
  137. transition: none;
  138. }
  139. `;
  140. const StyledButtonBar = styled(ButtonBar)`
  141. @media (min-width: ${props => props.theme.breakpoints.xxlarge}) {
  142. display: none;
  143. }
  144. `;