teamSelectForMember.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  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. <div>
  94. <TeamRoleColumnLabel />
  95. </div>
  96. <div>
  97. <DropdownAddTeam
  98. disabled={disabled}
  99. isLoadingTeams={isLoadingTeams}
  100. isAddingTeamToMember
  101. canCreateTeam={false}
  102. onSearch={onSearch}
  103. onSelect={onAddTeam}
  104. onCreateTeam={onCreateTeam}
  105. organization={organization}
  106. selectedTeams={selectedTeams.map(tm => tm.slug)}
  107. teams={teams}
  108. />
  109. </div>
  110. </TeamPanelHeader>
  111. <PanelBody>{loadingTeams ? <LoadingIndicator /> : renderBody()}</PanelBody>
  112. </Panel>
  113. );
  114. }
  115. function TeamRow({
  116. disabled,
  117. organization,
  118. team,
  119. member,
  120. onRemoveTeam,
  121. onChangeTeamRole,
  122. }: {
  123. disabled: boolean;
  124. member: Member;
  125. onChangeTeamRole: Props['onChangeTeamRole'];
  126. onRemoveTeam: Props['onRemoveTeam'];
  127. organization: Organization;
  128. team: Team;
  129. }) {
  130. const isIdpProvisioned = team.flags['idp:provisioned'];
  131. const isRemoveDisabled = disabled || isIdpProvisioned;
  132. const buttonHelpText = getButtonHelpText(isIdpProvisioned);
  133. return (
  134. <TeamPanelItem data-test-id="team-row-for-member">
  135. <div>
  136. <Link to={`/settings/${organization.slug}/teams/${team.slug}/`}>
  137. <TeamBadge team={team} />
  138. </Link>
  139. </div>
  140. <div>
  141. <TeamRoleSelect
  142. disabled={disabled}
  143. size="xs"
  144. organization={organization}
  145. team={team}
  146. member={member}
  147. onChangeTeamRole={newRole => onChangeTeamRole(team.slug, newRole)}
  148. />
  149. </div>
  150. <div>
  151. <Button
  152. size="xs"
  153. icon={<IconSubtract isCircled />}
  154. title={buttonHelpText}
  155. disabled={isRemoveDisabled}
  156. onClick={() => onRemoveTeam(team.slug)}
  157. >
  158. {t('Remove')}
  159. </Button>
  160. </div>
  161. </TeamPanelItem>
  162. );
  163. }
  164. const GRID_TEMPLATE = `
  165. display: grid;
  166. grid-template-columns: minmax(100px, 1fr) minmax(0px, 100px) 200px 95px;
  167. gap: ${space(1)};
  168. > div:last-child {
  169. margin-left: auto;
  170. }
  171. `;
  172. const TeamPanelHeader = styled(PanelHeader)`
  173. ${GRID_TEMPLATE}
  174. `;
  175. const TeamPanelItem = styled(PanelItem)`
  176. ${GRID_TEMPLATE}
  177. padding: ${space(2)};
  178. `;
  179. export default TeamSelect;