Browse Source

fix(issues) Improve smart search bar assignee suggestions (#12049)

Refetch the member list on each project change. This allows the assigned
autocomplete values to reflect only members in the current project.

Fixes APP-1117
Mark Story 6 years ago
parent
commit
3d98ceeae7

+ 4 - 2
src/sentry/static/sentry/app/actionCreators/members.jsx

@@ -1,9 +1,11 @@
 import MemberActions from 'app/actions/memberActions';
 import MemberListStore from 'app/stores/memberListStore';
 
-export function fetchOrgMembers(api, orgId) {
+export function fetchOrgMembers(api, orgId, projectIds = null) {
   const endpoint = `/organizations/${orgId}/users/`;
-  const promise = api.requestPromise(endpoint, {method: 'GET'});
+  const query = projectIds ? {project: projectIds} : null;
+
+  const promise = api.requestPromise(endpoint, {method: 'GET', query});
   return promise.then(members => {
     members = members.filter(m => m.user);
 

+ 2 - 0
src/sentry/static/sentry/app/components/smartSearchBar.jsx

@@ -61,6 +61,7 @@ class SmartSearchBar extends React.Component {
     onGetTagValues: PropTypes.func,
 
     onSearch: PropTypes.func,
+
     // If true, excludes the environment tag from the autocompletion list
     // This is because we don't want to treat environment as a tag in some places
     // such as the stream view where it is a top level concept
@@ -551,6 +552,7 @@ const SmartSearchBarContainer = withOrganization(
     },
 
     render() {
+      // SmartSearchBar doesn't use members, but we forward it to cause a re-render.
       return <SmartSearchBar {...this.props} members={this.state.members} />;
     },
   })

+ 16 - 3
src/sentry/static/sentry/app/views/organizationStream/overview.jsx

@@ -97,9 +97,7 @@ const OrganizationStream = createReactClass({
     });
 
     fetchTags(this.props.organization.slug);
-    fetchOrgMembers(this.api, this.props.organization.slug).then(members => {
-      this.setState({memberList: indexMembersByProject(members)});
-    });
+    this.fetchMemberList();
 
     // Start by getting searches first so if the user is on a saved search
     // we load the correct data the first time.
@@ -122,6 +120,12 @@ const OrganizationStream = createReactClass({
       }
     }
 
+    // If the project selection has changed reload the member list
+    // allowing autocomplete to be more accurate.
+    if (!isEqual(prevProps.selection.projects, this.props.selection.projects)) {
+      this.fetchMemberList();
+    }
+
     const prevQuery = prevProps.location.query;
     const newQuery = this.props.location.query;
 
@@ -223,6 +227,15 @@ const OrganizationStream = createReactClass({
     return this.props.organization.projects.filter(p => projects.indexOf(p.id) > -1);
   },
 
+  fetchMemberList() {
+    const projects = this.getGlobalSearchProjects();
+    const projectIds = projects.map(p => p.id);
+
+    fetchOrgMembers(this.api, this.props.organization.slug, projectIds).then(members => {
+      this.setState({memberList: indexMembersByProject(members)});
+    });
+  },
+
   fetchData() {
     GroupStore.loadInitialData([]);
 

+ 29 - 2
tests/js/spec/views/organizationStream/overview.spec.jsx

@@ -22,6 +22,7 @@ describe('OrganizationStream', function() {
   let savedSearch;
 
   let fetchTagsRequest;
+  let fetchMembersRequest;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
@@ -74,7 +75,7 @@ describe('OrganizationStream', function() {
       method: 'GET',
       body: TestStubs.Tags(),
     });
-    MockApiClient.addMockResponse({
+    fetchMembersRequest = MockApiClient.addMockResponse({
       url: '/organizations/org-slug/users/',
       method: 'GET',
       body: [TestStubs.Member({projects: [project.slug]})],
@@ -165,6 +166,8 @@ describe('OrganizationStream', function() {
       await instance.componentDidMount();
       await wrapper.update();
 
+      expect(fetchMembersRequest).toHaveBeenCalled();
+
       const members = instance.state.memberList;
       // Spot check the resulting structure as we munge it a bit.
       expect(members).toBeTruthy();
@@ -215,7 +218,7 @@ describe('OrganizationStream', function() {
     });
   });
 
-  describe('componentDidUpdate', function() {
+  describe('componentDidUpdate fetching groups', function() {
     let fetchDataMock;
     beforeEach(function() {
       fetchDataMock = jest.fn();
@@ -254,6 +257,30 @@ describe('OrganizationStream', function() {
     });
   });
 
+  describe('componentDidUpdate fetching members', function() {
+    beforeEach(function() {
+      wrapper = shallow(<OrganizationStream {...props} />, {
+        disableLifecycleMethods: false,
+      });
+      wrapper.instance().fetchData = jest.fn();
+    });
+
+    it('fetches memberlist on project change', function() {
+      // Called during componentDidMount
+      expect(fetchMembersRequest).toHaveBeenCalledTimes(1);
+
+      const selection = {
+        projects: [99],
+        environments: [],
+        datetime: {period: '24h'},
+      };
+      wrapper.setProps({selection});
+      wrapper.update();
+
+      expect(fetchMembersRequest).toHaveBeenCalledTimes(2);
+    });
+  });
+
   describe('processingIssues', function() {
     beforeEach(function() {
       wrapper = shallow(<OrganizationStream {...props} />);