actions.tsx 5.2 KB

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