teamSelectForProject.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import React from 'react';
  2. import styled from '@emotion/styled';
  3. import {Button} from 'sentry/components/button';
  4. import Confirm from 'sentry/components/confirm';
  5. import EmptyMessage from 'sentry/components/emptyMessage';
  6. import {TeamBadge} from 'sentry/components/idBadge/teamBadge';
  7. import Link from 'sentry/components/links/link';
  8. import LoadingIndicator from 'sentry/components/loadingIndicator';
  9. import {Panel, PanelBody, PanelHeader, PanelItem} from 'sentry/components/panels';
  10. import {IconSubtract} from 'sentry/icons';
  11. import {t} from 'sentry/locale';
  12. import {space} from 'sentry/styles/space';
  13. import {Organization, Project, Team} from 'sentry/types';
  14. import {useTeams} from 'sentry/utils/useTeams';
  15. import {DropdownAddTeam, TeamSelectProps} from './utils';
  16. type Props = TeamSelectProps & {
  17. canCreateTeam: boolean;
  18. project: Project;
  19. /**
  20. * Used when showing Teams for a Project
  21. */
  22. selectedTeams: Team[];
  23. };
  24. function TeamSelect({
  25. disabled,
  26. canCreateTeam,
  27. project,
  28. selectedTeams,
  29. organization,
  30. onAddTeam,
  31. onRemoveTeam,
  32. onCreateTeam,
  33. }: Props) {
  34. const renderBody = () => {
  35. const numTeams = selectedTeams.length;
  36. if (numTeams === 0) {
  37. return <EmptyMessage>{t('No Teams assigned')}</EmptyMessage>;
  38. }
  39. // If the user is not a team-admin in any parent teams of this project, they will
  40. // not be able to edit the configuration. Warn the user if this is their last team
  41. // where they have team-admin role.
  42. const isUserLastTeamWrite =
  43. selectedTeams.reduce(
  44. (count, team) => (team.access.includes('team:write') ? count + 1 : count),
  45. 0
  46. ) === 1;
  47. const isOnlyTeam = numTeams === 1;
  48. const confirmMessage = isUserLastTeamWrite
  49. ? t(
  50. "This is the last team that grants Team Admin access to you for this project. After removing this team, you will not be able to edit this project's configuration."
  51. )
  52. : isOnlyTeam
  53. ? t(
  54. 'This is the last team with access to this project. After removing this team, only organization owners and managers will be able to access the project pages.'
  55. )
  56. : null;
  57. return (
  58. <React.Fragment>
  59. {selectedTeams.map(team => (
  60. <TeamRow
  61. key={team.slug}
  62. disabled={disabled || !team.access.includes('team:write')}
  63. confirmMessage={confirmMessage}
  64. organization={organization}
  65. team={team}
  66. onRemoveTeam={slug => onRemoveTeam(slug)}
  67. />
  68. ))}
  69. </React.Fragment>
  70. );
  71. };
  72. const {teams, onSearch, fetching: isLoadingTeams} = useTeams();
  73. return (
  74. <Panel>
  75. <PanelHeader hasButtons>
  76. {t('Team')}
  77. <DropdownAddTeam
  78. disabled={disabled}
  79. isLoadingTeams={isLoadingTeams}
  80. isAddingTeamToProject
  81. canCreateTeam={canCreateTeam}
  82. onSearch={onSearch}
  83. onSelect={onAddTeam}
  84. onCreateTeam={onCreateTeam}
  85. organization={organization}
  86. selectedTeams={selectedTeams.map(tm => tm.slug)}
  87. teams={teams}
  88. project={project}
  89. />
  90. </PanelHeader>
  91. <PanelBody>{isLoadingTeams ? <LoadingIndicator /> : renderBody()}</PanelBody>
  92. </Panel>
  93. );
  94. }
  95. function TeamRow({
  96. organization,
  97. team,
  98. onRemoveTeam,
  99. disabled,
  100. confirmMessage,
  101. }: {
  102. confirmMessage: string | null;
  103. disabled: boolean;
  104. onRemoveTeam: Props['onRemoveTeam'];
  105. organization: Organization;
  106. team: Team;
  107. }) {
  108. return (
  109. <TeamPanelItem data-test-id="team-row-for-project">
  110. <TeamPanelItemLeft>
  111. <Link to={`/settings/${organization.slug}/teams/${team.slug}/`}>
  112. <TeamBadge team={team} />
  113. </Link>
  114. </TeamPanelItemLeft>
  115. <Confirm
  116. message={confirmMessage}
  117. bypass={!confirmMessage}
  118. onConfirm={() => onRemoveTeam(team.slug)}
  119. disabled={disabled}
  120. >
  121. <Button size="xs" icon={<IconSubtract isCircled size="xs" />} disabled={disabled}>
  122. {t('Remove')}
  123. </Button>
  124. </Confirm>
  125. </TeamPanelItem>
  126. );
  127. }
  128. const TeamPanelItem = styled(PanelItem)`
  129. padding: ${space(2)};
  130. align-items: center;
  131. justify-content: space-between;
  132. `;
  133. const TeamPanelItemLeft = styled('div')`
  134. flex-grow: 4;
  135. `;
  136. export default TeamSelect;