integrationExternalTeamMappings.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import * as React from 'react';
  2. import {addErrorMessage, addSuccessMessage} from 'app/actionCreators/indicator';
  3. import {openModal} from 'app/actionCreators/modal';
  4. import AsyncComponent from 'app/components/asyncComponent';
  5. import IntegrationExternalMappingForm from 'app/components/integrationExternalMappingForm';
  6. import IntegrationExternalMappings from 'app/components/integrationExternalMappings';
  7. import {t} from 'app/locale';
  8. import {ExternalActorMapping, Integration, Organization, Team} from 'app/types';
  9. import withOrganization from 'app/utils/withOrganization';
  10. import FormModel from 'app/views/settings/components/forms/model';
  11. type Props = AsyncComponent['props'] & {
  12. integration: Integration;
  13. organization: Organization;
  14. };
  15. type State = AsyncComponent['state'] & {
  16. teams: Team[];
  17. queryResults: Team[];
  18. };
  19. class IntegrationExternalTeamMappings extends AsyncComponent<Props, State> {
  20. getDefaultState() {
  21. return {
  22. ...super.getDefaultState(),
  23. teams: [],
  24. queryResults: [],
  25. };
  26. }
  27. getEndpoints(): ReturnType<AsyncComponent['getEndpoints']> {
  28. const {organization} = this.props;
  29. return [
  30. [
  31. 'teams',
  32. `/organizations/${organization.slug}/teams/`,
  33. {query: {query: 'hasExternalTeams:true'}},
  34. ],
  35. ];
  36. }
  37. handleDelete = async (mapping: ExternalActorMapping) => {
  38. try {
  39. const {organization} = this.props;
  40. const {teams} = this.state;
  41. const team = teams.find(item => item.id === mapping.teamId);
  42. if (!team) {
  43. throw new Error('Cannot find correct team slug.');
  44. }
  45. const endpoint = `/teams/${organization.slug}/${team.slug}/external-teams/${mapping.id}/`;
  46. await this.api.requestPromise(endpoint, {
  47. method: 'DELETE',
  48. });
  49. // remove config and update state
  50. addSuccessMessage(t('Deletion successful'));
  51. this.fetchData();
  52. } catch {
  53. //no 4xx errors should happen on delete
  54. addErrorMessage(t('An error occurred'));
  55. }
  56. };
  57. handleSubmitSuccess = () => {
  58. this.fetchData();
  59. };
  60. get mappings() {
  61. const {integration} = this.props;
  62. const {teams} = this.state;
  63. const externalTeamMappings = teams.reduce((acc, team) => {
  64. const {externalTeams} = team;
  65. acc.push(
  66. ...externalTeams
  67. .filter(externalTeam => externalTeam.provider === integration.provider.key)
  68. .map(externalTeam => ({...externalTeam, sentryName: team.name}))
  69. );
  70. return acc;
  71. }, [] as ExternalActorMapping[]);
  72. return externalTeamMappings.sort((a, b) => parseInt(a.id, 10) - parseInt(b.id, 10));
  73. }
  74. sentryNamesMapper(teams: Team[]) {
  75. return teams.map(({id, name}) => ({id, name}));
  76. }
  77. handleSubmit = (
  78. data: Record<string, any>,
  79. onSubmitSuccess: (data: Record<string, any>) => void,
  80. onSubmitError: (error: any) => void,
  81. _: React.FormEvent<Element>,
  82. model: FormModel,
  83. mapping?: ExternalActorMapping
  84. ) => {
  85. // We need to dynamically set the endpoint bc it requires the slug of the selected team in the form.
  86. try {
  87. const {organization} = this.props;
  88. const {queryResults} = this.state;
  89. const team = queryResults.find(item => item.id === data.teamId);
  90. if (!team) {
  91. throw new Error('Cannot find team slug.');
  92. }
  93. const baseEndpoint = `/teams/${organization.slug}/${team.slug}/external-teams/`;
  94. const apiEndpoint = mapping ? `${baseEndpoint}${mapping.id}/` : baseEndpoint;
  95. const apiMethod = mapping ? 'PUT' : 'POST';
  96. model.setFormOptions({
  97. onSubmitSuccess,
  98. onSubmitError,
  99. apiEndpoint,
  100. apiMethod,
  101. });
  102. model.saveForm();
  103. } catch {
  104. //no 4xx errors should happen on delete
  105. addErrorMessage(t('An error occurred'));
  106. }
  107. };
  108. openModal = (mapping?: ExternalActorMapping) => {
  109. const {organization, integration} = this.props;
  110. openModal(({Body, Header, closeModal}) => (
  111. <React.Fragment>
  112. <Header closeButton>{t('Configure External Team Mapping')}</Header>
  113. <Body>
  114. <IntegrationExternalMappingForm
  115. organization={organization}
  116. integration={integration}
  117. onSubmitSuccess={() => {
  118. this.handleSubmitSuccess();
  119. closeModal();
  120. }}
  121. mapping={mapping}
  122. sentryNamesMapper={this.sentryNamesMapper}
  123. type="team"
  124. url={`/organizations/${organization.slug}/teams/`}
  125. onCancel={closeModal}
  126. onSubmit={(...args) => this.handleSubmit(...args, mapping)}
  127. onResults={results => this.setState({queryResults: results})}
  128. />
  129. </Body>
  130. </React.Fragment>
  131. ));
  132. };
  133. renderBody() {
  134. const {integration} = this.props;
  135. return (
  136. <IntegrationExternalMappings
  137. integration={integration}
  138. type="team"
  139. mappings={this.mappings}
  140. onCreateOrEdit={this.openModal}
  141. onDelete={this.handleDelete}
  142. />
  143. );
  144. }
  145. }
  146. export default withOrganization(IntegrationExternalTeamMappings);