teamKeyTransactionsManager.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import {Component, createContext} from 'react';
  2. import isEqual from 'lodash/isEqual';
  3. import {
  4. fetchTeamKeyTransactions,
  5. TeamKeyTransactions,
  6. toggleKeyTransaction,
  7. } from 'sentry/actionCreators/performance';
  8. import {Client} from 'sentry/api';
  9. import {t} from 'sentry/locale';
  10. import {Organization, Project, Team} from 'sentry/types';
  11. import withApi from 'sentry/utils/withApi';
  12. export type TeamSelection = {
  13. action: 'key' | 'unkey';
  14. project: Project;
  15. teamIds: string[];
  16. transactionName: string;
  17. };
  18. export type TeamKeyTransactionManagerChildrenProps = {
  19. counts: Map<string, number> | null;
  20. error: string | null;
  21. getKeyedTeams: (project: string, transactionName: string) => Set<string> | null;
  22. handleToggleKeyTransaction: (selection: TeamSelection) => void;
  23. isLoading: boolean;
  24. teams: Team[];
  25. };
  26. const TeamKeyTransactionsManagerContext =
  27. createContext<TeamKeyTransactionManagerChildrenProps>({
  28. teams: [],
  29. isLoading: false,
  30. error: null,
  31. counts: null,
  32. getKeyedTeams: () => null,
  33. handleToggleKeyTransaction: () => {},
  34. });
  35. type Props = {
  36. api: Client;
  37. children: React.ReactNode;
  38. organization: Organization;
  39. selectedTeams: string[];
  40. teams: Team[];
  41. selectedProjects?: string[];
  42. };
  43. type State = Omit<
  44. TeamKeyTransactionManagerChildrenProps,
  45. 'teams' | 'counts' | 'getKeyedTeams' | 'handleToggleKeyTransaction'
  46. > & {
  47. keyFetchID: symbol | null;
  48. teamKeyTransactions: TeamKeyTransactions;
  49. };
  50. class UnwrappedProvider extends Component<Props> {
  51. state: State = {
  52. keyFetchID: null,
  53. isLoading: true,
  54. error: null,
  55. teamKeyTransactions: [],
  56. };
  57. componentDidMount() {
  58. this.fetchData();
  59. }
  60. componentDidUpdate(prevProps: Props) {
  61. const orgSlugChanged = prevProps.organization.slug !== this.props.organization.slug;
  62. const selectedTeamsChanged = !isEqual(
  63. prevProps.selectedTeams,
  64. this.props.selectedTeams
  65. );
  66. const selectedProjectsChanged = !isEqual(
  67. prevProps.selectedProjects,
  68. this.props.selectedProjects
  69. );
  70. if (orgSlugChanged || selectedTeamsChanged || selectedProjectsChanged) {
  71. this.fetchData();
  72. }
  73. }
  74. async fetchData() {
  75. const {api, organization, selectedTeams, selectedProjects} = this.props;
  76. const keyFetchID = Symbol('keyFetchID');
  77. this.setState({isLoading: true, keyFetchID});
  78. let teamKeyTransactions: TeamKeyTransactions = [];
  79. let error: string | null = null;
  80. try {
  81. teamKeyTransactions = await fetchTeamKeyTransactions(
  82. api,
  83. organization.slug,
  84. selectedTeams,
  85. selectedProjects
  86. );
  87. } catch (err) {
  88. error = err.responseJSON?.detail ?? t('Error fetching team key transactions');
  89. }
  90. this.setState({
  91. isLoading: false,
  92. keyFetchID: undefined,
  93. error,
  94. teamKeyTransactions,
  95. });
  96. }
  97. getCounts() {
  98. const {teamKeyTransactions} = this.state;
  99. const counts: Map<string, number> = new Map();
  100. teamKeyTransactions.forEach(({team, count}) => {
  101. counts.set(team, count);
  102. });
  103. return counts;
  104. }
  105. getKeyedTeams = (projectId: string, transactionName: string) => {
  106. const {teamKeyTransactions} = this.state;
  107. const keyedTeams: Set<string> = new Set();
  108. teamKeyTransactions.forEach(({team, keyed}) => {
  109. const isKeyedByTeam = keyed.find(
  110. keyedTeam =>
  111. keyedTeam.project_id === projectId && keyedTeam.transaction === transactionName
  112. );
  113. if (isKeyedByTeam) {
  114. keyedTeams.add(team);
  115. }
  116. });
  117. return keyedTeams;
  118. };
  119. handleToggleKeyTransaction = async (selection: TeamSelection) => {
  120. const {api, organization} = this.props;
  121. const {teamKeyTransactions} = this.state;
  122. const {action, project, transactionName, teamIds} = selection;
  123. const isKeyTransaction = action === 'unkey';
  124. const teamIdSet = new Set(teamIds);
  125. const newTeamKeyTransactions = teamKeyTransactions.map(({team, count, keyed}) => {
  126. if (!teamIdSet.has(team)) {
  127. return {team, count, keyed};
  128. }
  129. if (isKeyTransaction) {
  130. return {
  131. team,
  132. count: count - 1,
  133. keyed: keyed.filter(
  134. keyTransaction =>
  135. keyTransaction.project_id !== project.id ||
  136. keyTransaction.transaction !== transactionName
  137. ),
  138. };
  139. }
  140. return {
  141. team,
  142. count: count + 1,
  143. keyed: [
  144. ...keyed,
  145. {
  146. project_id: project.id,
  147. transaction: transactionName,
  148. },
  149. ],
  150. };
  151. });
  152. try {
  153. await toggleKeyTransaction(
  154. api,
  155. isKeyTransaction,
  156. organization.slug,
  157. [project.id],
  158. transactionName,
  159. teamIds
  160. );
  161. this.setState({teamKeyTransactions: newTeamKeyTransactions});
  162. } catch (err) {
  163. this.setState({
  164. error: err.responseJSON?.detail ?? null,
  165. });
  166. }
  167. };
  168. render() {
  169. const {teams} = this.props;
  170. const {isLoading, error} = this.state;
  171. const childrenProps: TeamKeyTransactionManagerChildrenProps = {
  172. teams,
  173. isLoading,
  174. error,
  175. counts: this.getCounts(),
  176. getKeyedTeams: this.getKeyedTeams,
  177. handleToggleKeyTransaction: this.handleToggleKeyTransaction,
  178. };
  179. return (
  180. <TeamKeyTransactionsManagerContext.Provider value={childrenProps}>
  181. {this.props.children}
  182. </TeamKeyTransactionsManagerContext.Provider>
  183. );
  184. }
  185. }
  186. export const Provider = withApi(UnwrappedProvider);
  187. export const Consumer = TeamKeyTransactionsManagerContext.Consumer;