Просмотр исходного кода

ref(ui): Readonly project store state (#72513)

Scott Cooper 9 месяцев назад
Родитель
Сommit
fcdf5dd3d9
2 измененных файлов с 44 добавлено и 28 удалено
  1. 6 0
      static/app/stores/projectsStore.spec.tsx
  2. 38 28
      static/app/stores/projectsStore.tsx

+ 6 - 0
static/app/stores/projectsStore.spec.tsx

@@ -165,4 +165,10 @@ describe('ProjectsStore', function () {
       });
     });
   });
+
+  it('should return a stable reference from getState', function () {
+    ProjectsStore.loadInitialData([projectFoo, projectBar]);
+    const state = ProjectsStore.getState();
+    expect(Object.is(state, ProjectsStore.getState())).toBe(true);
+  });
 });

+ 38 - 28
static/app/stores/projectsStore.tsx

@@ -6,7 +6,7 @@ import type {Team} from 'sentry/types/organization';
 import type {Project} from 'sentry/types/project';
 
 import LatestContextStore from './latestContextStore';
-import type {CommonStoreDefinition} from './types';
+import type {StrictStoreDefinition} from './types';
 
 type State = {
   loading: boolean;
@@ -20,17 +20,14 @@ type StatsData = Record<string, Project['stats']>;
  */
 type InternalDefinition = {
   api: Client;
-  loading: boolean;
-  projects: Project[];
   removeTeamFromProject(teamSlug: string, project: Project): void;
 };
 
 interface ProjectsStoreDefinition
   extends InternalDefinition,
-    CommonStoreDefinition<State> {
+    StrictStoreDefinition<State> {
   getById(id?: string): Project | undefined;
   getBySlug(slug?: string): Project | undefined;
-  init(): void;
   isLoading(): boolean;
   loadInitialData(projects: Project[]): void;
   onAddTeam(team: Team, projectSlug: string): void;
@@ -45,9 +42,10 @@ interface ProjectsStoreDefinition
 
 const storeConfig: ProjectsStoreDefinition = {
   api: new Client(),
-
-  projects: [],
-  loading: true,
+  state: {
+    projects: [],
+    loading: true,
+  },
 
   init() {
     // XXX: Do not use `this.listenTo` in this store. We avoid usage of reflux
@@ -57,14 +55,17 @@ const storeConfig: ProjectsStoreDefinition = {
   },
 
   reset() {
-    this.projects = [];
-    this.itemsById = {};
-    this.loading = true;
+    this.state = {
+      projects: [],
+      loading: true,
+    };
   },
 
   loadInitialData(items: Project[]) {
-    this.projects = items.toSorted((a, b) => a.slug.localeCompare(b.slug));
-    this.loading = false;
+    this.state = {
+      projects: items.toSorted((a, b) => a.slug.localeCompare(b.slug)),
+      loading: false,
+    };
 
     this.trigger(new Set(items.map(x => x.id)));
   },
@@ -77,17 +78,19 @@ const storeConfig: ProjectsStoreDefinition = {
     }
 
     const newProject = {...prevProject, slug: newSlug};
-    this.projects = this.projects
+    const newProjects = this.state.projects
       .map(project => (project.slug === prevSlug ? newProject : project))
-      .sort((a, b) => a.slug.localeCompare(b.slug));
+      .toSorted((a, b) => a.slug.localeCompare(b.slug));
+    this.state = {...this.state, projects: newProjects};
 
     this.trigger(new Set([prevProject.id]));
   },
 
   onCreateSuccess(project: Project, orgSlug: string) {
-    this.projects = this.projects
+    const newProjects = this.state.projects
       .concat([project])
       .sort((a, b) => a.slug.localeCompare(b.slug));
+    this.state = {...this.state, projects: newProjects};
 
     // Reload organization details since we've created a new project
     fetchOrganizationDetails(this.api, orgSlug, true, false);
@@ -103,7 +106,10 @@ const storeConfig: ProjectsStoreDefinition = {
     }
 
     const newProject = {...project, ...data};
-    this.projects = this.projects.map(p => (p.id === project.id ? newProject : p));
+    const newProjects = this.state.projects.map(p =>
+      p.id === project.id ? newProject : p
+    );
+    this.state = {...this.state, projects: newProjects};
 
     this.trigger(new Set([data.id]));
 
@@ -114,9 +120,10 @@ const storeConfig: ProjectsStoreDefinition = {
     const statsData = data || {};
 
     // Assign stats into projects
-    this.projects = this.projects.map(project =>
+    const newProjects = this.state.projects.map(project =>
       statsData[project.id] ? {...project, stats: data[project.id]} : project
     );
+    this.state = {...this.state, projects: newProjects};
 
     this.trigger(new Set(Object.keys(data)));
   },
@@ -128,7 +135,7 @@ const storeConfig: ProjectsStoreDefinition = {
    */
   onDeleteTeam(teamSlug: string) {
     // Look for team in all projects
-    const projects = this.projects.filter(({teams}) =>
+    const projects = this.state.projects.filter(({teams}) =>
       teams.find(({slug}) => slug === teamSlug)
     );
 
@@ -158,7 +165,10 @@ const storeConfig: ProjectsStoreDefinition = {
     }
 
     const newProject = {...project, teams: [...project.teams, team]};
-    this.projects = this.projects.map(p => (p.id === project.id ? newProject : p));
+    const newProjects = this.state.projects.map(p =>
+      p.id === project.id ? newProject : p
+    );
+    this.state = {...this.state, projects: newProjects};
 
     this.trigger(new Set([project.id]));
   },
@@ -167,26 +177,26 @@ const storeConfig: ProjectsStoreDefinition = {
   removeTeamFromProject(teamSlug: string, project: Project) {
     const newTeams = project.teams.filter(({slug}) => slug !== teamSlug);
     const newProject = {...project, teams: newTeams};
-    this.projects = this.projects.map(p => (p.id === project.id ? newProject : p));
+    const newProjects = this.state.projects.map(p =>
+      p.id === project.id ? newProject : p
+    );
+    this.state = {...this.state, projects: newProjects};
   },
 
   isLoading() {
-    return this.loading;
+    return this.state.loading;
   },
 
   getById(id) {
-    return this.projects.find(project => project.id === id);
+    return this.state.projects.find(project => project.id === id);
   },
 
   getBySlug(slug) {
-    return this.projects.find(project => project.slug === slug);
+    return this.state.projects.find(project => project.slug === slug);
   },
 
   getState() {
-    return {
-      projects: this.projects,
-      loading: this.loading,
-    };
+    return this.state;
   },
 };