Browse Source

feat(ui): Have <Projects> utility component populate the project store (#15253)

Changed <Projects> to have the capability of fetching a list of all projects and populating that list into the store. This will be one of the last pieces needed for a user in the lightweight org route tree to be able to smoothly navigate to the heavyweight org tree.

Refs SEN-1195
David Wang 5 years ago
parent
commit
f78d1af529

+ 17 - 3
src/sentry/static/sentry/app/utils/projects.jsx

@@ -2,6 +2,7 @@ import {memoize, partition, uniqBy} from 'lodash';
 import PropTypes from 'prop-types';
 import React from 'react';
 
+import ProjectActions from 'app/actions/projectActions';
 import SentryTypes from 'app/sentryTypes';
 import parseLinkHeader from 'app/utils/parseLinkHeader';
 import withApi from 'app/utils/withApi';
@@ -29,6 +30,10 @@ class Projects extends React.Component {
 
     // Number of projects to return when not using `props.slugs`
     limit: PropTypes.number,
+
+    // Whether to fetch all the projects in the organization of which the user
+    // has access to
+    allProjects: PropTypes.bool,
   };
 
   state = {
@@ -151,14 +156,14 @@ class Projects extends React.Component {
    * results using search
    */
   loadAllProjects = async () => {
-    const {api, orgId, limit} = this.props;
+    const {api, orgId, limit, allProjects} = this.props;
 
     this.setState({
       fetching: true,
     });
 
     try {
-      const {results, hasMore} = await fetchProjects(api, orgId, {limit});
+      const {results, hasMore} = await fetchProjects(api, orgId, {limit, allProjects});
 
       this.setState({
         fetching: false,
@@ -252,7 +257,7 @@ class Projects extends React.Component {
 
 export default withProjects(withApi(Projects));
 
-async function fetchProjects(api, orgId, {slugs, search, limit} = {}) {
+async function fetchProjects(api, orgId, {slugs, search, limit, allProjects} = {}) {
   const query = {};
 
   if (slugs && slugs.length) {
@@ -268,6 +273,10 @@ async function fetchProjects(api, orgId, {slugs, search, limit} = {}) {
     query.per_page = limit;
   }
 
+  if (allProjects) {
+    query.all_projects = 1;
+  }
+
   let hasMore = false;
   const [results, _, xhr] = await api.requestPromise(
     `/organizations/${orgId}/projects/`,
@@ -286,6 +295,11 @@ async function fetchProjects(api, orgId, {slugs, search, limit} = {}) {
       (paginationObject.next.results || paginationObject.previous.results);
   }
 
+  // populate the projects store if all projects were fetched
+  if (allProjects) {
+    ProjectActions.loadProjects(results);
+  }
+
   return {
     results,
     hasMore,

+ 62 - 0
tests/js/spec/utils/projects.spec.jsx

@@ -3,6 +3,7 @@ import {mount} from 'sentry-test/enzyme';
 
 import Projects from 'app/utils/projects';
 import ProjectsStore from 'app/stores/projectsStore';
+import ProjectActions from 'app/actions/projectActions';
 
 describe('utils.projects', function() {
   const renderer = jest.fn(() => null);
@@ -434,5 +435,66 @@ describe('utils.projects', function() {
         })
       );
     });
+
+    it('can query for a list of all projects and save it to the store', async function() {
+      const loadProjects = jest.spyOn(ProjectActions, 'loadProjects');
+      const mockProjects = [
+        TestStubs.Project({
+          id: '100',
+          slug: 'a',
+        }),
+        TestStubs.Project({
+          id: '101',
+          slug: 'b',
+        }),
+        TestStubs.Project({
+          id: '102',
+          slug: 'c',
+        }),
+      ];
+
+      request.mockClear();
+      request = MockApiClient.addMockResponse({
+        url: '/organizations/org-slug/projects/',
+        query: {
+          all_projects: '1',
+        },
+        body: mockProjects,
+      });
+
+      const wrapper = createWrapper({allProjects: true});
+
+      // This is initial state
+      expect(renderer).toHaveBeenCalledWith(
+        expect.objectContaining({
+          fetching: true,
+          isIncomplete: null,
+          hasMore: null,
+          projects: [],
+        })
+      );
+
+      // wait for request to resolve
+      await tick();
+      wrapper.update();
+      expect(request).toHaveBeenCalledWith(
+        expect.anything(),
+        expect.objectContaining({
+          query: {all_projects: 1},
+        })
+      );
+
+      expect(renderer).toHaveBeenCalledWith(
+        expect.objectContaining({
+          fetching: false,
+          isIncomplete: null,
+          hasMore: false,
+          projects: mockProjects,
+        })
+      );
+
+      // expect the store action to be called
+      expect(loadProjects).toHaveBeenCalledWith(mockProjects);
+    });
   });
 });