teamStore.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import {createStore} from 'reflux';
  2. import TeamActions from 'sentry/actions/teamActions';
  3. import {Team} from 'sentry/types';
  4. import {defined} from 'sentry/utils';
  5. import {makeSafeRefluxStore} from 'sentry/utils/makeSafeRefluxStore';
  6. import {CommonStoreDefinition} from './types';
  7. type State = {
  8. cursor: string | null;
  9. hasMore: boolean | null;
  10. loadedUserTeams: boolean;
  11. loading: boolean;
  12. teams: Team[];
  13. };
  14. interface TeamStoreDefinition extends CommonStoreDefinition<State> {
  15. getAll(): Team[];
  16. getById(id: string): Team | null;
  17. getBySlug(slug: string): Team | null;
  18. init(): void;
  19. initialized: boolean;
  20. loadInitialData(items: Team[], hasMore?: boolean | null, cursor?: string | null): void;
  21. onCreateSuccess(team: Team): void;
  22. onRemoveSuccess(slug: string): void;
  23. onUpdateSuccess(itemId: string, response: Team): void;
  24. reset(): void;
  25. state: State;
  26. }
  27. const teamStoreConfig: TeamStoreDefinition = {
  28. initialized: false,
  29. state: {
  30. teams: [],
  31. loadedUserTeams: false,
  32. loading: true,
  33. hasMore: null,
  34. cursor: null,
  35. },
  36. unsubscribeListeners: [],
  37. init() {
  38. this.reset();
  39. this.unsubscribeListeners.push(
  40. this.listenTo(TeamActions.createTeamSuccess, this.onCreateSuccess)
  41. );
  42. this.unsubscribeListeners.push(
  43. this.listenTo(TeamActions.fetchDetailsSuccess, this.onUpdateSuccess)
  44. );
  45. this.unsubscribeListeners.push(
  46. this.listenTo(TeamActions.loadTeams, this.loadInitialData)
  47. );
  48. this.unsubscribeListeners.push(
  49. this.listenTo(TeamActions.loadUserTeams, this.loadUserTeams)
  50. );
  51. this.unsubscribeListeners.push(
  52. this.listenTo(TeamActions.removeTeamSuccess, this.onRemoveSuccess)
  53. );
  54. this.unsubscribeListeners.push(
  55. this.listenTo(TeamActions.updateSuccess, this.onUpdateSuccess)
  56. );
  57. this.unsubscribeListeners.push(this.listenTo(TeamActions.reset, this.reset));
  58. },
  59. reset() {
  60. this.state = {
  61. teams: [],
  62. loadedUserTeams: false,
  63. loading: true,
  64. hasMore: null,
  65. cursor: null,
  66. };
  67. },
  68. setTeams(teams, hasMore, cursor) {
  69. this.initialized = true;
  70. this.state = {
  71. teams,
  72. loadedUserTeams: defined(hasMore) ? !hasMore : this.state.loadedUserTeams,
  73. loading: false,
  74. hasMore: hasMore ?? this.state.hasMore,
  75. cursor: cursor ?? this.state.cursor,
  76. };
  77. this.trigger(new Set(teams.map(team => team.id)));
  78. },
  79. loadInitialData(items, hasMore, cursor) {
  80. const teams = this.updateTeams(items);
  81. this.setTeams(teams, hasMore, cursor);
  82. },
  83. loadUserTeams(userTeams: Team[]) {
  84. const teams = this.updateTeams(userTeams);
  85. this.state = {
  86. ...this.state,
  87. loadedUserTeams: true,
  88. teams,
  89. };
  90. this.trigger(new Set(teams.map(team => team.id)));
  91. },
  92. onUpdateSuccess(itemId, response) {
  93. if (!response) {
  94. return;
  95. }
  96. const item = this.getBySlug(itemId);
  97. if (!item) {
  98. this.state = {
  99. ...this.state,
  100. teams: [...this.state.teams, response],
  101. };
  102. this.trigger(new Set([itemId]));
  103. return;
  104. }
  105. // Slug was changed
  106. // Note: This is the proper way to handle slug changes but unfortunately not all of our
  107. // components use stores correctly. To be safe reload browser :((
  108. if (response.slug !== itemId) {
  109. // Replace the team
  110. const teams = [...this.state.teams.filter(({slug}) => slug !== itemId), response];
  111. this.state = {...this.state, teams};
  112. this.trigger(new Set([response.slug]));
  113. return;
  114. }
  115. const newTeams = [...this.state.teams];
  116. const index = newTeams.findIndex(team => team.slug === response.slug);
  117. newTeams[index] = response;
  118. this.state = {...this.state, teams: newTeams};
  119. this.trigger(new Set([itemId]));
  120. },
  121. onRemoveSuccess(slug: string) {
  122. const teams = this.state.teams.filter(team => team.slug !== slug);
  123. this.setTeams(teams);
  124. },
  125. onCreateSuccess(team: Team) {
  126. this.loadInitialData([team]);
  127. },
  128. getState() {
  129. return this.state;
  130. },
  131. getById(id: string) {
  132. const {teams} = this.state;
  133. return teams.find(item => item.id.toString() === id.toString()) || null;
  134. },
  135. getBySlug(slug: string) {
  136. const {teams} = this.state;
  137. return teams.find(item => item.slug === slug) || null;
  138. },
  139. getAll() {
  140. return this.state.teams;
  141. },
  142. updateTeams(teams: Team[]) {
  143. const teamIdMap = this.state.teams.reduce((acc: Record<string, Team>, team: Team) => {
  144. acc[team.id] = team;
  145. return acc;
  146. }, {});
  147. // Replace or insert new user teams
  148. teams.reduce((acc: Record<string, Team>, userTeam: Team) => {
  149. acc[userTeam.id] = userTeam;
  150. return acc;
  151. }, teamIdMap);
  152. return Object.values(teamIdMap).sort((a, b) => a.slug.localeCompare(b.slug));
  153. },
  154. };
  155. const TeamStore = createStore(makeSafeRefluxStore(teamStoreConfig));
  156. export default TeamStore;