repositoryRow.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import styled from '@emotion/styled';
  2. import {cancelDeleteRepository, hideRepository} from 'sentry/actionCreators/integrations';
  3. import {Client} from 'sentry/api';
  4. import Access from 'sentry/components/acl/access';
  5. import {Button} from 'sentry/components/button';
  6. import Confirm from 'sentry/components/confirm';
  7. import ExternalLink from 'sentry/components/links/externalLink';
  8. import PanelItem from 'sentry/components/panels/panelItem';
  9. import {Tooltip} from 'sentry/components/tooltip';
  10. import {IconDelete} from 'sentry/icons';
  11. import {t} from 'sentry/locale';
  12. import {space} from 'sentry/styles/space';
  13. import {Repository, RepositoryStatus} from 'sentry/types';
  14. type Props = {
  15. api: Client;
  16. orgSlug: string;
  17. repository: Repository;
  18. onRepositoryChange?: (data: Repository) => void;
  19. showProvider?: boolean;
  20. };
  21. function getStatusLabel(repo: Repository) {
  22. switch (repo.status) {
  23. case RepositoryStatus.PENDING_DELETION:
  24. return 'Deletion Queued';
  25. case RepositoryStatus.DELETION_IN_PROGRESS:
  26. return 'Deletion in Progress';
  27. case RepositoryStatus.DISABLED:
  28. return 'Disabled';
  29. case RepositoryStatus.HIDDEN:
  30. return 'Disabled';
  31. default:
  32. return null;
  33. }
  34. }
  35. function RepositoryRow({
  36. api,
  37. repository,
  38. onRepositoryChange,
  39. orgSlug,
  40. showProvider = false,
  41. }: Props) {
  42. const isActive = repository.status === RepositoryStatus.ACTIVE;
  43. const cancelDelete = () =>
  44. cancelDeleteRepository(api, orgSlug, repository.id).then(
  45. data => {
  46. if (onRepositoryChange) {
  47. onRepositoryChange(data);
  48. }
  49. },
  50. () => {}
  51. );
  52. const deleteRepo = () =>
  53. hideRepository(api, orgSlug, repository.id).then(
  54. data => {
  55. if (onRepositoryChange) {
  56. onRepositoryChange(data);
  57. }
  58. },
  59. () => {}
  60. );
  61. const renderDeleteButton = (hasAccess: boolean) => (
  62. <Tooltip
  63. title={t(
  64. 'You must be an organization owner, manager or admin to remove a repository.'
  65. )}
  66. disabled={hasAccess}
  67. >
  68. <Confirm
  69. disabled={
  70. !hasAccess || (!isActive && repository.status !== RepositoryStatus.DISABLED)
  71. }
  72. onConfirm={deleteRepo}
  73. message={t(
  74. 'Are you sure you want to remove this repository? All associated commit data will be removed in addition to the repository.'
  75. )}
  76. >
  77. <StyledButton
  78. size="xs"
  79. icon={<IconDelete />}
  80. aria-label={t('delete')}
  81. disabled={!hasAccess}
  82. />
  83. </Confirm>
  84. </Tooltip>
  85. );
  86. return (
  87. <Access access={['org:integrations']}>
  88. {({hasAccess}) => (
  89. <StyledPanelItem status={repository.status}>
  90. <RepositoryTitleAndUrl>
  91. <RepositoryTitle>
  92. <strong>{repository.name}</strong>
  93. {!isActive && <small> &mdash; {getStatusLabel(repository)}</small>}
  94. {repository.status === RepositoryStatus.PENDING_DELETION && (
  95. <StyledButton
  96. size="xs"
  97. onClick={cancelDelete}
  98. disabled={!hasAccess}
  99. data-test-id="repo-cancel"
  100. >
  101. {t('Cancel')}
  102. </StyledButton>
  103. )}
  104. </RepositoryTitle>
  105. <div>
  106. {showProvider && <small>{repository.provider.name}</small>}
  107. {showProvider && repository.url && <span>&nbsp;&mdash;&nbsp;</span>}
  108. {repository.url && (
  109. <small>
  110. <ExternalLink href={repository.url}>
  111. {repository.url.replace('https://', '')}
  112. </ExternalLink>
  113. </small>
  114. )}
  115. </div>
  116. </RepositoryTitleAndUrl>
  117. {renderDeleteButton(hasAccess)}
  118. </StyledPanelItem>
  119. )}
  120. </Access>
  121. );
  122. }
  123. const StyledPanelItem = styled(PanelItem)<{status: RepositoryStatus}>`
  124. /* shorter top padding because of title lineheight */
  125. padding: ${space(1)} ${space(2)} ${space(2)};
  126. justify-content: space-between;
  127. align-items: center;
  128. flex: 1;
  129. ${p =>
  130. p.status === RepositoryStatus.DISABLED &&
  131. `
  132. filter: grayscale(1);
  133. opacity: 0.4;
  134. `};
  135. &:last-child {
  136. border-bottom: none;
  137. }
  138. `;
  139. const StyledButton = styled(Button)`
  140. margin-left: ${space(1)};
  141. `;
  142. const RepositoryTitleAndUrl = styled('div')`
  143. display: flex;
  144. flex-direction: column;
  145. `;
  146. const RepositoryTitle = styled('div')`
  147. /* accommodate cancel button height */
  148. line-height: 26px;
  149. `;
  150. export default RepositoryRow;