Browse Source

feat(team-store): Support user teams state in TeamStore (#28957)

Augment the team store with state relating to whether or not the user's teams are loaded. This will be useful to avoid continually refetching the user's teams.
David Wang 3 years ago
parent
commit
8f6d02383a

+ 6 - 0
static/app/actionCreators/teams.tsx

@@ -47,6 +47,12 @@ export function fetchTeams(api: Client, params: OrgSlug, options: CallbackOption
   });
   });
 }
 }
 
 
+// Fetch user teams for current org and place them in the team store
+export async function fetchUserTeams(api: Client, params: OrgSlug) {
+  const teams = await api.requestPromise(`/organizations/${params.orgId}/user-teams/`);
+  TeamActions.loadUserTeams(teams);
+}
+
 export function fetchTeamDetails(
 export function fetchTeamDetails(
   api: Client,
   api: Client,
   params: OrgAndTeamSlug,
   params: OrgAndTeamSlug,

+ 1 - 0
static/app/actions/teamActions.tsx

@@ -11,6 +11,7 @@ const TeamActions = Reflux.createActions([
   'fetchDetailsError',
   'fetchDetailsError',
   'fetchDetailsSuccess',
   'fetchDetailsSuccess',
   'loadTeams',
   'loadTeams',
+  'loadUserTeams',
   'removeTeam',
   'removeTeam',
   'removeTeamError',
   'removeTeamError',
   'removeTeamSuccess',
   'removeTeamSuccess',

+ 30 - 1
static/app/stores/teamStore.tsx

@@ -3,10 +3,13 @@ import Reflux from 'reflux';
 import TeamActions from 'app/actions/teamActions';
 import TeamActions from 'app/actions/teamActions';
 import {Team} from 'app/types';
 import {Team} from 'app/types';
 
 
+const MAX_TEAMS = 100;
+
 type State = {
 type State = {
   teams: Team[];
   teams: Team[];
   loading: boolean;
   loading: boolean;
   hasMore: boolean | null;
   hasMore: boolean | null;
+  loadedUserTeams: boolean;
 };
 };
 
 
 type TeamStoreInterface = {
 type TeamStoreInterface = {
@@ -27,6 +30,7 @@ const teamStoreConfig: Reflux.StoreDefinition & TeamStoreInterface = {
   initialized: false,
   initialized: false,
   state: {
   state: {
     teams: [],
     teams: [],
+    loadedUserTeams: false,
     loading: true,
     loading: true,
     hasMore: null,
     hasMore: null,
   },
   },
@@ -37,24 +41,49 @@ const teamStoreConfig: Reflux.StoreDefinition & TeamStoreInterface = {
     this.listenTo(TeamActions.createTeamSuccess, this.onCreateSuccess);
     this.listenTo(TeamActions.createTeamSuccess, this.onCreateSuccess);
     this.listenTo(TeamActions.fetchDetailsSuccess, this.onUpdateSuccess);
     this.listenTo(TeamActions.fetchDetailsSuccess, this.onUpdateSuccess);
     this.listenTo(TeamActions.loadTeams, this.loadInitialData);
     this.listenTo(TeamActions.loadTeams, this.loadInitialData);
+    this.listenTo(TeamActions.loadUserTeams, this.loadUserTeams);
     this.listenTo(TeamActions.removeTeamSuccess, this.onRemoveSuccess);
     this.listenTo(TeamActions.removeTeamSuccess, this.onRemoveSuccess);
     this.listenTo(TeamActions.updateSuccess, this.onUpdateSuccess);
     this.listenTo(TeamActions.updateSuccess, this.onUpdateSuccess);
   },
   },
 
 
   reset() {
   reset() {
-    this.state = {teams: [], loading: true, hasMore: null};
+    this.state = {teams: [], loadedUserTeams: false, loading: true, hasMore: null};
   },
   },
 
 
   loadInitialData(items, hasMore = null) {
   loadInitialData(items, hasMore = null) {
     this.initialized = true;
     this.initialized = true;
     this.state = {
     this.state = {
       teams: items.sort((a, b) => a.slug.localeCompare(b.slug)),
       teams: items.sort((a, b) => a.slug.localeCompare(b.slug)),
+      // TODO(davidenwang): Replace with a more reliable way of knowing when we have loaded all teams
+      loadedUserTeams: items.length < MAX_TEAMS,
       loading: false,
       loading: false,
       hasMore,
       hasMore,
     };
     };
     this.trigger(new Set(items.map(item => item.id)));
     this.trigger(new Set(items.map(item => item.id)));
   },
   },
 
 
+  loadUserTeams(userTeams: Team[]) {
+    const teamIdMap = this.state.teams.reduce((acc: Record<string, Team>, team: Team) => {
+      acc[team.id] = team;
+      return acc;
+    }, {});
+
+    // Replace or insert new user teams
+    userTeams.reduce((acc: Record<string, Team>, userTeam: Team) => {
+      acc[userTeam.id] = userTeam;
+      return acc;
+    }, teamIdMap);
+
+    const teams = Object.values(teamIdMap).sort((a, b) => a.slug.localeCompare(b.slug));
+    this.state = {
+      ...this.state,
+      loadedUserTeams: true,
+      teams,
+    };
+
+    this.trigger(new Set(Object.keys(teamIdMap)));
+  },
+
   onUpdateSuccess(itemId, response) {
   onUpdateSuccess(itemId, response) {
     if (!response) {
     if (!response) {
       return;
       return;

+ 93 - 0
tests/js/spec/stores/teamStore.spec.jsx

@@ -0,0 +1,93 @@
+import TeamActions from 'app/actions/teamActions';
+import TeamStore from 'app/stores/teamStore';
+
+describe('TeamStore', function () {
+  const teamFoo = TestStubs.Team({
+    slug: 'team-foo',
+  });
+  const teamBar = TestStubs.Team({
+    slug: 'team-bar',
+  });
+
+  describe('setting data', function () {
+    beforeEach(function () {
+      TeamStore.reset();
+    });
+
+    it('populate teams correctly', async function () {
+      expect(TeamStore.get()).toMatchObject({
+        teams: [],
+        loading: true,
+        hasMore: null,
+        loadedUserTeams: false,
+      });
+
+      TeamActions.loadTeams([teamFoo, teamBar]);
+      await tick();
+      expect(TeamStore.get()).toMatchObject({
+        teams: [teamBar, teamFoo],
+        loading: false,
+        hasMore: null,
+        loadedUserTeams: true,
+      });
+    });
+
+    it('loads user teams', async function () {
+      expect(TeamStore.get()).toMatchObject({
+        teams: [],
+        loadedUserTeams: false,
+      });
+
+      TeamActions.loadUserTeams([teamFoo]);
+      await tick();
+      expect(TeamStore.get()).toMatchObject({
+        teams: [teamFoo],
+        loadedUserTeams: true,
+      });
+    });
+  });
+
+  describe('updating teams', function () {
+    it('adds new teams', async function () {
+      TeamActions.loadTeams([teamFoo]);
+      await tick();
+      expect(TeamStore.get()).toMatchObject({
+        teams: [teamFoo],
+      });
+
+      TeamActions.createTeamSuccess(teamBar);
+      await tick();
+      expect(TeamStore.get()).toMatchObject({
+        teams: [teamBar, teamFoo],
+      });
+    });
+
+    it('removes teams', async function () {
+      TeamActions.loadTeams([teamFoo]);
+      await tick();
+      expect(TeamStore.get()).toMatchObject({
+        teams: [teamFoo],
+      });
+
+      TeamActions.removeTeamSuccess(teamFoo.slug);
+      await tick();
+      expect(TeamStore.get()).toMatchObject({
+        teams: [],
+      });
+    });
+
+    it('updates teams', async function () {
+      TeamActions.loadTeams([teamFoo]);
+      await tick();
+      expect(TeamStore.get()).toMatchObject({
+        teams: [teamFoo],
+      });
+
+      TeamActions.updateSuccess(teamFoo.slug, teamBar);
+      await tick();
+      expect(TeamStore.get()).toMatchObject({
+        teams: [teamBar],
+      });
+    });
+  });
+});