projectTeams.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import {RouteComponentProps} from 'react-router';
  2. import {addErrorMessage} from 'sentry/actionCreators/indicator';
  3. import {openCreateTeamModal} from 'sentry/actionCreators/modal';
  4. import {addTeamToProject, removeTeamFromProject} from 'sentry/actionCreators/projects';
  5. import {t, tct} from 'sentry/locale';
  6. import TeamStore from 'sentry/stores/teamStore';
  7. import {Organization, Project, Team} from 'sentry/types';
  8. import routeTitleGen from 'sentry/utils/routeTitle';
  9. import AsyncView from 'sentry/views/asyncView';
  10. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  11. import TeamSelectForProject from 'sentry/views/settings/components/teamSelect/teamSelectForProject';
  12. type Props = {
  13. organization: Organization;
  14. project: Project;
  15. } & RouteComponentProps<{projectId: string}, {}>;
  16. type State = {
  17. projectTeams: null | Team[];
  18. } & AsyncView['state'];
  19. class ProjectTeams extends AsyncView<Props, State> {
  20. getEndpoints(): ReturnType<AsyncView['getEndpoints']> {
  21. const {organization, project} = this.props;
  22. return [['projectTeams', `/projects/${organization.slug}/${project.slug}/teams/`]];
  23. }
  24. getTitle() {
  25. const {projectId} = this.props.params;
  26. return routeTitleGen(t('Project Teams'), projectId, false);
  27. }
  28. canCreateTeam = () => {
  29. const access = this.props.organization.access;
  30. return (
  31. access.includes('org:write') &&
  32. access.includes('team:write') &&
  33. access.includes('project:write')
  34. );
  35. };
  36. handleRemove = (teamSlug: Team['slug']) => {
  37. if (this.state.loading) {
  38. return;
  39. }
  40. const {organization, project} = this.props;
  41. removeTeamFromProject(this.api, organization.slug, project.slug, teamSlug)
  42. .then(() => this.handleRemovedTeam(teamSlug))
  43. .catch(() => {
  44. addErrorMessage(t('Could not remove the %s team', teamSlug));
  45. this.setState({loading: false});
  46. });
  47. };
  48. handleRemovedTeam = (teamSlug: Team['slug']) => {
  49. this.setState(prevState => ({
  50. projectTeams: [
  51. ...(prevState.projectTeams || []).filter(team => team.slug !== teamSlug),
  52. ],
  53. }));
  54. };
  55. handleAddedTeam = (team: Team) => {
  56. this.setState(prevState => ({
  57. projectTeams: [...(prevState.projectTeams || []), team],
  58. }));
  59. };
  60. handleAdd = (teamSlug: string) => {
  61. if (this.state.loading) {
  62. return;
  63. }
  64. const team = TeamStore.getBySlug(teamSlug);
  65. if (!team) {
  66. addErrorMessage(tct('Unable to find "[teamSlug]"', {teamSlug}));
  67. this.setState({error: true});
  68. return;
  69. }
  70. const {organization, project} = this.props;
  71. addTeamToProject(this.api, organization.slug, project.slug, team).then(
  72. () => {
  73. this.handleAddedTeam(team);
  74. },
  75. () => {
  76. this.setState({
  77. error: true,
  78. loading: false,
  79. });
  80. }
  81. );
  82. };
  83. handleCreateTeam = (e: React.MouseEvent) => {
  84. e.stopPropagation();
  85. e.preventDefault();
  86. const {project, organization} = this.props;
  87. if (!this.canCreateTeam()) {
  88. return;
  89. }
  90. openCreateTeamModal({
  91. project,
  92. organization,
  93. onClose: data => {
  94. addTeamToProject(this.api, organization.slug, project.slug, data).then(
  95. this.remountComponent,
  96. this.remountComponent
  97. );
  98. },
  99. });
  100. };
  101. renderBody() {
  102. const {project, organization} = this.props;
  103. const canCreateTeam = this.canCreateTeam();
  104. const hasAccess = organization.access.includes('project:write');
  105. const confirmRemove = t(
  106. 'This is the last team with access to this project. Removing it will mean only organization owners and managers will be able to access the project pages. Are you sure you want to remove this team from the project %s?',
  107. project.slug
  108. );
  109. const {projectTeams} = this.state;
  110. return (
  111. <div>
  112. <SettingsPageHeader title={t('Project Teams for %s', project.slug)} />
  113. <TeamSelectForProject
  114. disabled={!hasAccess}
  115. canCreateTeam={canCreateTeam}
  116. organization={organization}
  117. project={project}
  118. selectedTeams={projectTeams ?? []}
  119. onAddTeam={this.handleAdd}
  120. onRemoveTeam={this.handleRemove}
  121. onCreateTeam={(team: Team) => {
  122. addTeamToProject(this.api, organization.slug, project.slug, team).then(
  123. this.remountComponent,
  124. this.remountComponent
  125. );
  126. }}
  127. confirmLastTeamRemoveMessage={confirmRemove}
  128. />
  129. </div>
  130. );
  131. }
  132. }
  133. export default ProjectTeams;