teamSelectForMember.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import {Button} from 'sentry/components/button';
  4. import EmptyMessage from 'sentry/components/emptyMessage';
  5. import {TeamBadge} from 'sentry/components/idBadge/teamBadge';
  6. import Link from 'sentry/components/links/link';
  7. import LoadingIndicator from 'sentry/components/loadingIndicator';
  8. import Panel from 'sentry/components/panels/panel';
  9. import PanelBody from 'sentry/components/panels/panelBody';
  10. import PanelHeader from 'sentry/components/panels/panelHeader';
  11. import PanelItem from 'sentry/components/panels/panelItem';
  12. import TeamRoleSelect from 'sentry/components/teamRoleSelect';
  13. import {TeamRoleColumnLabel} from 'sentry/components/teamRoleUtils';
  14. import {IconSubtract} from 'sentry/icons';
  15. import {t} from 'sentry/locale';
  16. import {space} from 'sentry/styles/space';
  17. import type {Member, Organization, Team} from 'sentry/types/organization';
  18. import {useTeams} from 'sentry/utils/useTeams';
  19. import {RoleOverwritePanelAlert} from 'sentry/views/settings/organizationTeams/roleOverwriteWarning';
  20. import {getButtonHelpText} from 'sentry/views/settings/organizationTeams/utils';
  21. import type {TeamSelectProps} from './utils';
  22. import {DropdownAddTeam} from './utils';
  23. type Props = TeamSelectProps & {
  24. /**
  25. * Member that this component is acting upon
  26. */
  27. member: Member;
  28. /**
  29. * Used when showing Teams for a Member
  30. */
  31. onChangeTeamRole: (teamSlug: string, teamRole: string) => void;
  32. /**
  33. * Used when showing Teams for a Member
  34. */
  35. selectedOrgRole: Member['orgRole'];
  36. /**
  37. * Used when showing Teams for a Member
  38. */
  39. selectedTeamRoles: Member['teamRoles'];
  40. };
  41. function TeamSelect({
  42. disabled,
  43. loadingTeams,
  44. member,
  45. selectedOrgRole,
  46. selectedTeamRoles,
  47. organization,
  48. onAddTeam,
  49. onRemoveTeam,
  50. onCreateTeam,
  51. onChangeTeamRole,
  52. }: Props) {
  53. const {teams, onSearch, fetching: isLoadingTeams} = useTeams();
  54. const {orgRoleList, teamRoleList} = organization;
  55. const selectedTeamSlugs = new Set(selectedTeamRoles.map(tm => tm.teamSlug));
  56. const selectedTeams = teams.filter(tm => selectedTeamSlugs.has(tm.slug));
  57. const renderBody = () => {
  58. if (selectedTeams.length === 0) {
  59. return <EmptyMessage>{t('No Teams assigned')}</EmptyMessage>;
  60. }
  61. return (
  62. <Fragment>
  63. {selectedOrgRole && (
  64. <RoleOverwritePanelAlert
  65. orgRole={selectedOrgRole}
  66. orgRoleList={orgRoleList}
  67. teamRoleList={teamRoleList}
  68. />
  69. )}
  70. {selectedTeams.map(team => (
  71. <TeamRow
  72. key={team.slug}
  73. disabled={disabled}
  74. organization={organization}
  75. team={team}
  76. member={{
  77. ...member,
  78. orgRole: selectedOrgRole,
  79. teamRoles: selectedTeamRoles,
  80. }}
  81. onChangeTeamRole={onChangeTeamRole}
  82. onRemoveTeam={slug => onRemoveTeam(slug)}
  83. />
  84. ))}
  85. </Fragment>
  86. );
  87. };
  88. return (
  89. <Panel>
  90. <TeamPanelHeader hasButtons>
  91. <div>{t('Team')}</div>
  92. <div>
  93. <TeamRoleColumnLabel />
  94. </div>
  95. <div>
  96. <DropdownAddTeam
  97. disabled={disabled}
  98. isLoadingTeams={isLoadingTeams}
  99. isAddingTeamToMember
  100. canCreateTeam={false}
  101. onSearch={onSearch}
  102. onSelect={onAddTeam}
  103. onCreateTeam={onCreateTeam}
  104. organization={organization}
  105. selectedTeams={selectedTeams.map(tm => tm.slug)}
  106. teams={teams}
  107. />
  108. </div>
  109. </TeamPanelHeader>
  110. <PanelBody>{loadingTeams ? <LoadingIndicator /> : renderBody()}</PanelBody>
  111. </Panel>
  112. );
  113. }
  114. function TeamRow({
  115. disabled,
  116. organization,
  117. team,
  118. member,
  119. onRemoveTeam,
  120. onChangeTeamRole,
  121. }: {
  122. disabled: boolean;
  123. member: Member;
  124. onChangeTeamRole: Props['onChangeTeamRole'];
  125. onRemoveTeam: Props['onRemoveTeam'];
  126. organization: Organization;
  127. team: Team;
  128. }) {
  129. const isIdpProvisioned = team.flags['idp:provisioned'];
  130. const isRemoveDisabled = disabled || isIdpProvisioned;
  131. const buttonHelpText = getButtonHelpText(isIdpProvisioned);
  132. return (
  133. <TeamPanelItem data-test-id="team-row-for-member">
  134. <div>
  135. <Link to={`/settings/${organization.slug}/teams/${team.slug}/`}>
  136. <TeamBadge team={team} />
  137. </Link>
  138. </div>
  139. <div style={{whiteSpace: 'nowrap'}}>
  140. <TeamRoleSelect
  141. disabled={disabled}
  142. size="xs"
  143. organization={organization}
  144. team={team}
  145. member={member}
  146. onChangeTeamRole={newRole => onChangeTeamRole(team.slug, newRole)}
  147. />
  148. </div>
  149. <div>
  150. <Button
  151. size="xs"
  152. icon={<IconSubtract isCircled />}
  153. title={buttonHelpText}
  154. disabled={isRemoveDisabled}
  155. onClick={() => onRemoveTeam(team.slug)}
  156. >
  157. {t('Remove')}
  158. </Button>
  159. </div>
  160. </TeamPanelItem>
  161. );
  162. }
  163. const GRID_TEMPLATE = `
  164. display: grid;
  165. grid-template-columns: minmax(100px, 1fr) minmax(0px, 100px) 200px 95px;
  166. gap: ${space(1)};
  167. > div:last-child {
  168. margin-left: auto;
  169. }
  170. `;
  171. const TeamPanelHeader = styled(PanelHeader)`
  172. ${GRID_TEMPLATE}
  173. `;
  174. const TeamPanelItem = styled(PanelItem)`
  175. ${GRID_TEMPLATE}
  176. padding: ${space(2)};
  177. `;
  178. export default TeamSelect;