organizationTeams.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import {useState} from 'react';
  2. import {RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import debounce from 'lodash/debounce';
  5. import partition from 'lodash/partition';
  6. import {openCreateTeamModal} from 'sentry/actionCreators/modal';
  7. import Button from 'sentry/components/button';
  8. import LoadingIndicator from 'sentry/components/loadingIndicator';
  9. import {Panel, PanelBody, PanelHeader} from 'sentry/components/panels';
  10. import SearchBar from 'sentry/components/searchBar';
  11. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  12. import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants';
  13. import {IconAdd} from 'sentry/icons';
  14. import {t} from 'sentry/locale';
  15. import space from 'sentry/styles/space';
  16. import {AccessRequest, Organization} from 'sentry/types';
  17. import useTeams from 'sentry/utils/useTeams';
  18. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  19. import {RoleOverwritePanelAlert} from 'sentry/views/settings/organizationTeams/roleOverwriteWarning';
  20. import AllTeamsList from './allTeamsList';
  21. import OrganizationAccessRequests from './organizationAccessRequests';
  22. type Props = {
  23. access: Set<string>;
  24. features: Set<string>;
  25. onRemoveAccessRequest: (id: string, isApproved: boolean) => void;
  26. organization: Organization;
  27. requestList: AccessRequest[];
  28. } & RouteComponentProps<{orgId: string}, {}>;
  29. function OrganizationTeams({
  30. organization,
  31. access,
  32. features,
  33. params,
  34. requestList,
  35. onRemoveAccessRequest,
  36. }: Props) {
  37. const [teamQuery, setTeamQuery] = useState('');
  38. const {initiallyLoaded} = useTeams({provideUserTeams: true});
  39. const {teams, onSearch, loadMore, hasMore, fetching} = useTeams();
  40. if (!organization) {
  41. return null;
  42. }
  43. const canCreateTeams = access.has('project:admin');
  44. const action = (
  45. <Button
  46. priority="primary"
  47. size="sm"
  48. disabled={!canCreateTeams}
  49. title={
  50. !canCreateTeams ? t('You do not have permission to create teams') : undefined
  51. }
  52. onClick={() =>
  53. openCreateTeamModal({
  54. organization,
  55. })
  56. }
  57. icon={<IconAdd size="xs" isCircled />}
  58. >
  59. {t('Create Team')}
  60. </Button>
  61. );
  62. const title = t('Teams');
  63. const debouncedSearch = debounce(onSearch, DEFAULT_DEBOUNCE_DURATION);
  64. function handleSearch(query: string) {
  65. setTeamQuery(query);
  66. debouncedSearch(query);
  67. }
  68. const {slug: orgSlug, orgRole, orgRoleList, teamRoleList} = organization;
  69. const filteredTeams = teams.filter(team =>
  70. `#${team.slug}`.toLowerCase().includes(teamQuery.toLowerCase())
  71. );
  72. const [userTeams, otherTeams] = partition(filteredTeams, team => team.isMember);
  73. return (
  74. <div data-test-id="team-list">
  75. <SentryDocumentTitle title={title} orgSlug={orgSlug} />
  76. <SettingsPageHeader title={title} action={action} />
  77. <OrganizationAccessRequests
  78. orgId={params.orgId}
  79. requestList={requestList}
  80. onRemoveAccessRequest={onRemoveAccessRequest}
  81. />
  82. <StyledSearchBar
  83. placeholder={t('Search teams')}
  84. onChange={handleSearch}
  85. query={teamQuery}
  86. />
  87. <Panel>
  88. <PanelHeader>{t('Your Teams')}</PanelHeader>
  89. <PanelBody>
  90. {features.has('team-roles') && (
  91. <RoleOverwritePanelAlert
  92. orgRole={orgRole}
  93. orgRoleList={orgRoleList}
  94. teamRoleList={teamRoleList}
  95. isSelf
  96. />
  97. )}
  98. {initiallyLoaded ? (
  99. <AllTeamsList
  100. organization={organization}
  101. teamList={userTeams.filter(team => team.slug.includes(teamQuery))}
  102. access={access}
  103. openMembership={false}
  104. />
  105. ) : (
  106. <LoadingIndicator />
  107. )}
  108. </PanelBody>
  109. </Panel>
  110. <Panel>
  111. <PanelHeader>{t('Other Teams')}</PanelHeader>
  112. <PanelBody>
  113. <AllTeamsList
  114. organization={organization}
  115. teamList={otherTeams}
  116. access={access}
  117. openMembership={
  118. !!(features.has('open-membership') || access.has('org:write'))
  119. }
  120. />
  121. </PanelBody>
  122. </Panel>
  123. {hasMore && (
  124. <LoadMoreWrapper>
  125. {fetching && <LoadingIndicator mini />}
  126. <Button onClick={() => loadMore(teamQuery)}>{t('Show more')}</Button>
  127. </LoadMoreWrapper>
  128. )}
  129. </div>
  130. );
  131. }
  132. const StyledSearchBar = styled(SearchBar)`
  133. margin-bottom: ${space(2)};
  134. `;
  135. const LoadMoreWrapper = styled('div')`
  136. display: grid;
  137. gap: ${space(2)};
  138. align-items: center;
  139. justify-content: end;
  140. grid-auto-flow: column;
  141. `;
  142. export default OrganizationTeams;