teamSelectForProject.tsx 4.3 KB

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