teamKeyTransactionButton.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import {Component} from 'react';
  2. import styled from '@emotion/styled';
  3. import {
  4. fetchTeamKeyTransactions,
  5. toggleKeyTransaction,
  6. } from 'app/actionCreators/performance';
  7. import {Client} from 'app/api';
  8. import Button from 'app/components/button';
  9. import TeamKeyTransaction, {
  10. TitleProps,
  11. } from 'app/components/performance/teamKeyTransaction';
  12. import {IconStar} from 'app/icons';
  13. import {t, tn} from 'app/locale';
  14. import {Organization, Team} from 'app/types';
  15. import EventView from 'app/utils/discover/eventView';
  16. import withApi from 'app/utils/withApi';
  17. import withTeams from 'app/utils/withTeams';
  18. /**
  19. * This can't be a function component because `TeamKeyTransaction` uses
  20. * `DropdownControl` which in turn uses passes a ref to this component.
  21. */
  22. class TitleButton extends Component<TitleProps> {
  23. render() {
  24. const {keyedTeamsCount, ...props} = this.props;
  25. return (
  26. <StyledButton
  27. {...props}
  28. icon={keyedTeamsCount ? <IconStar color="yellow300" isSolid /> : <IconStar />}
  29. >
  30. {keyedTeamsCount
  31. ? tn('Starred for Team', 'Starred for Teams', keyedTeamsCount)
  32. : t('Star for Team')}
  33. </StyledButton>
  34. );
  35. }
  36. }
  37. type BaseProps = {
  38. api: Client;
  39. organization: Organization;
  40. transactionName: string;
  41. teams: Team[];
  42. };
  43. type Props = BaseProps & {
  44. project: number;
  45. };
  46. type State = {
  47. isLoading: boolean;
  48. keyFetchID: symbol | undefined;
  49. error: null | string;
  50. keyedTeams: Set<string>;
  51. counts: Map<string, number>;
  52. };
  53. class TeamKeyTransactionButton extends Component<Props, State> {
  54. state: State = {
  55. isLoading: true,
  56. keyFetchID: undefined,
  57. error: null,
  58. keyedTeams: new Set(),
  59. counts: new Map(),
  60. };
  61. componentDidMount() {
  62. this.fetchData();
  63. }
  64. componentDidUpdate(prevProps: Props) {
  65. const orgSlugChanged = prevProps.organization.slug !== this.props.organization.slug;
  66. const projectsChanged = prevProps.project !== this.props.project;
  67. const transactionChanged = prevProps.transactionName !== this.props.transactionName;
  68. if (orgSlugChanged || projectsChanged || transactionChanged) {
  69. this.fetchData();
  70. }
  71. }
  72. async fetchData() {
  73. const {api, organization, project, transactionName} = this.props;
  74. const keyFetchID = Symbol('keyFetchID');
  75. this.setState({isLoading: true, keyFetchID});
  76. let keyedTeams: Set<string> = new Set();
  77. let counts: Map<string, number> = new Map();
  78. let error: string | null = null;
  79. try {
  80. const teams = await fetchTeamKeyTransactions(
  81. api,
  82. organization.slug,
  83. ['myteams'],
  84. [String(project)]
  85. );
  86. keyedTeams = new Set(
  87. teams
  88. .filter(({keyed}) =>
  89. keyed.find(
  90. ({project_id, transaction}) =>
  91. project_id === String(project) && transaction === transactionName
  92. )
  93. )
  94. .map(({team}) => team)
  95. );
  96. counts = new Map(teams.map(({team, count}) => [team, count]));
  97. } catch (err) {
  98. error = err.responseJSON?.detail ?? t('Error fetching team key transactions');
  99. }
  100. this.setState({
  101. isLoading: false,
  102. keyFetchID: undefined,
  103. error,
  104. keyedTeams,
  105. counts,
  106. });
  107. }
  108. handleToggleKeyTransaction = async (
  109. isKey: boolean,
  110. teamIds: string[],
  111. counts: Map<string, number>,
  112. keyedTeams: Set<string>
  113. ) => {
  114. const {api, organization, project, transactionName} = this.props;
  115. try {
  116. await toggleKeyTransaction(
  117. api,
  118. isKey,
  119. organization.slug,
  120. [project],
  121. transactionName,
  122. teamIds
  123. );
  124. this.setState({
  125. counts,
  126. keyedTeams,
  127. });
  128. } catch (err) {
  129. this.setState({
  130. error: err.responseJSON?.detail ?? null,
  131. });
  132. }
  133. };
  134. render() {
  135. const {isLoading, error, counts, keyedTeams} = this.state;
  136. return (
  137. <TeamKeyTransaction
  138. isLoading={isLoading}
  139. error={error}
  140. counts={counts}
  141. keyedTeams={keyedTeams}
  142. handleToggleKeyTransaction={this.handleToggleKeyTransaction}
  143. title={TitleButton}
  144. {...this.props}
  145. />
  146. );
  147. }
  148. }
  149. type WrapperProps = BaseProps & {
  150. eventView: EventView;
  151. };
  152. function TeamKeyTransactionButtonWrapper({eventView, teams, ...props}: WrapperProps) {
  153. if (eventView.project.length !== 1) {
  154. return <TitleButton disabled keyedTeamsCount={0} />;
  155. }
  156. const userTeams = teams.filter(({isMember}) => isMember);
  157. return (
  158. <TeamKeyTransactionButton
  159. teams={userTeams}
  160. project={eventView.project[0]}
  161. {...props}
  162. />
  163. );
  164. }
  165. const StyledButton = styled(Button)`
  166. width: 180px;
  167. `;
  168. export default withApi(withTeams(TeamKeyTransactionButtonWrapper));