Browse Source

fix(ui): Fix projects dashboard after renaming a team (#17155)

This fixes Projects dashboard after renaming a team. The issue was that we were attempting to merge the old detailed organization with the updated lightweight organization (without teams), and `withTeamsForUser` was using the outdated `teams` from the detailed org.
Billy Vong 5 years ago
parent
commit
e9b72e1625

+ 1 - 1
src/sentry/static/sentry/app/actionCreators/organization.jsx

@@ -35,7 +35,7 @@ export async function fetchOrganizationDetails(api, slug, detailed, silent) {
       return;
     }
 
-    OrganizationActions.update(org);
+    OrganizationActions.update(org, {replace: true});
     setActiveOrganization(org);
 
     if (detailed) {

+ 2 - 2
src/sentry/static/sentry/app/stores/organizationStore.jsx

@@ -37,11 +37,11 @@ const OrganizationStore = Reflux.createStore({
     this.trigger(this.get());
   },
 
-  onUpdate(updatedOrg) {
+  onUpdate(updatedOrg, {replace = false} = {}) {
     this.loading = false;
     this.error = null;
     this.errorType = null;
-    this.organization = {...this.organization, ...updatedOrg};
+    this.organization = replace ? updatedOrg : {...this.organization, ...updatedOrg};
     this.dirty = false;
     this.trigger(this.get());
   },

+ 16 - 14
src/sentry/static/sentry/app/views/settings/organizationTeams/teamProjects.jsx

@@ -2,24 +2,25 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import styled from '@emotion/styled';
 
-import Tooltip from 'app/components/tooltip';
-import withApi from 'app/utils/withApi';
+import {Panel, PanelHeader, PanelBody, PanelItem} from 'app/components/panels';
 import {addErrorMessage, addSuccessMessage} from 'app/actionCreators/indicator';
-import space from 'app/styles/space';
+import {sortProjects} from 'app/utils';
+import {t} from 'app/locale';
 import Button from 'app/components/button';
 import DropdownAutoComplete from 'app/components/dropdownAutoComplete';
 import DropdownButton from 'app/components/dropdownButton';
 import EmptyMessage from 'app/views/settings/components/emptyMessage';
-import LoadingIndicator from 'app/components/loadingIndicator';
-import LoadingError from 'app/components/loadingError';
-import ProjectListItem from 'app/views/settings/components/settingsProjectItem';
-import {Panel, PanelHeader, PanelBody, PanelItem} from 'app/components/panels';
 import InlineSvg from 'app/components/inlineSvg';
+import LoadingError from 'app/components/loadingError';
+import LoadingIndicator from 'app/components/loadingIndicator';
 import Pagination from 'app/components/pagination';
-import {sortProjects} from 'app/utils';
-import {t} from 'app/locale';
-import withOrganization from 'app/utils/withOrganization';
+import ProjectActions from 'app/actions/projectActions';
+import ProjectListItem from 'app/views/settings/components/settingsProjectItem';
 import SentryTypes from 'app/sentryTypes';
+import Tooltip from 'app/components/tooltip';
+import space from 'app/styles/space';
+import withApi from 'app/utils/withApi';
+import withOrganization from 'app/utils/withOrganization';
 
 class TeamProjects extends React.Component {
   static propTypes = {
@@ -106,15 +107,16 @@ class TeamProjects extends React.Component {
     const {orgId, teamId} = this.props.params;
     this.props.api.request(`/projects/${orgId}/${project.slug}/teams/${teamId}/`, {
       method: action === 'add' ? 'POST' : 'DELETE',
-      success: () => {
+      success: resp => {
         this.fetchAll();
+        ProjectActions.updateSuccess(resp);
         addSuccessMessage(
           action === 'add'
             ? t('Successfully added project to team.')
             : t('Successfully removed project from team')
         );
       },
-      error: e => {
+      error: () => {
         addErrorMessage(t("Wasn't able to change project association."));
       },
     });
@@ -138,7 +140,7 @@ class TeamProjects extends React.Component {
     const canWrite = access.has('org:write');
 
     return projects.length ? (
-      sortProjects(projects).map((project, i) => (
+      sortProjects(projects).map(project => (
         <StyledPanelItem key={project.id}>
           <ProjectListItem project={project} organization={organization} />
           <Tooltip
@@ -206,7 +208,7 @@ class TeamProjects extends React.Component {
                   onSelect={this.handleProjectSelected}
                   emptyMessage={t('No projects')}
                 >
-                  {({isOpen, selectedItem}) => (
+                  {({isOpen}) => (
                     <DropdownButton isOpen={isOpen} size="xsmall">
                       {t('Add Project')}
                     </DropdownButton>

+ 39 - 27
tests/acceptance/test_dashboard.py

@@ -20,21 +20,15 @@ from datetime import datetime
 class DashboardTest(AcceptanceTestCase, SnubaTestCase):
     def setUp(self):
         super(DashboardTest, self).setUp()
-        self.user = self.create_user("foo@example.com")
-        self.org = self.create_organization(name="Rowdy Tiger", owner=None)
-        self.team = self.create_team(organization=self.org, name="Mariachi Band")
-        self.project = self.create_project(
-            organization=self.org, teams=[self.team], name="Bengal-Elephant-Giraffe-Tree-House"
-        )
-        self.create_member(user=self.user, organization=self.org, role="owner", teams=[self.team])
-
-        release = Release.objects.create(organization_id=self.org.id, version="1")
+        release = Release.objects.create(organization_id=self.organization.id, version="1")
 
-        environment = Environment.objects.create(organization_id=self.org.id, name="production")
+        environment = Environment.objects.create(
+            organization_id=self.organization.id, name="production"
+        )
 
         deploy = Deploy.objects.create(
             environment_id=environment.id,
-            organization_id=self.org.id,
+            organization_id=self.organization.id,
             release=release,
             date_finished="2018-05-23",
         )
@@ -47,17 +41,9 @@ class DashboardTest(AcceptanceTestCase, SnubaTestCase):
         )
 
         self.login_as(self.user)
-        self.path = u"/organizations/{}/projects/".format(self.org.slug)
-
-    def test_project_with_no_first_event(self):
-        self.project.update(first_event=None)
-        self.browser.get(self.path)
-        self.browser.wait_until_not(".loading-indicator")
-        self.browser.wait_until_test_id("resources")
-        self.browser.wait_until("[data-test-id] figure")
-        self.browser.snapshot("org dash no first event")
+        self.path = u"/organizations/{}/projects/".format(self.organization.slug)
 
-    def test_one_issue(self):
+    def create_sample_event(self):
         self.init_snuba()
 
         event_data = load_data("python")
@@ -77,21 +63,47 @@ class DashboardTest(AcceptanceTestCase, SnubaTestCase):
             status=OnboardingTaskStatus.COMPLETE,
         )
         self.project.update(first_event=timezone.now())
+
+    def test_project_with_no_first_event(self):
+        self.project.update(first_event=None)
         self.browser.get(self.path)
         self.browser.wait_until_not(".loading-indicator")
-        self.browser.wait_until("[data-test-id] figure")
+        self.browser.wait_until_test_id("resources")
+        self.browser.wait_until("[data-test-id] figure", timeout=10000)
+        self.browser.snapshot("org dash no first event")
+
+    def test_one_issue(self):
+        self.create_sample_event()
+        self.browser.get(self.path)
+        self.browser.wait_until_not(".loading-indicator")
+        self.browser.wait_until("[data-test-id] figure", timeout=100000)
         self.browser.snapshot("org dash one issue")
 
+    def test_rename_team_and_navigate_back(self):
+        self.create_sample_event()
+        self.browser.get(self.path)
+        self.browser.wait_until_not(".loading-indicator")
+        self.browser.click('[data-test-id="badge-display-name"]')
+        self.browser.wait_until_not(".loading-indicator")
+        self.browser.click(".nav-tabs li:nth-child(3) a")
+        self.browser.wait_until('input[name="slug"]')
+        self.browser.element('input[name="slug"]').send_keys("-new-slug")
+        self.browser.click('[aria-label="Save"]')
+        self.browser.wait_until_not('[aria-label="Save"]')
+        self.browser.wait_until('[data-test-id="toast-success"]')
+
+        # Go to projects
+        self.browser.click('[href="/organizations/{}/projects/"]'.format(self.organization.slug))
+        self.browser.wait_until_not(".loading-indicator")
+
+        assert self.browser.element('[data-test-id="badge-display-name"]').text == "#foo-new-slug"
+
 
 class EmptyDashboardTest(AcceptanceTestCase):
     def setUp(self):
         super(EmptyDashboardTest, self).setUp()
-        self.user = self.create_user("foo@example.com")
-        self.org = self.create_organization(name="Rowdy Tiger", owner=None)
-        self.team = self.create_team(organization=self.org, name="Mariachi Band")
-        self.create_member(user=self.user, organization=self.org, role="owner", teams=[self.team])
         self.login_as(self.user)
-        self.path = u"/organizations/{}/projects/".format(self.org.slug)
+        self.path = u"/organizations/{}/projects/".format(self.organization.slug)
 
     def test_new_dashboard_empty(self):
         self.browser.get(self.path)

+ 3 - 3
tests/js/spec/actionCreators/organization.spec.jsx

@@ -45,7 +45,7 @@ describe('OrganizationActionCreator', function() {
       `/organizations/${detailedOrg.slug}/`,
       expect.anything()
     );
-    expect(OrganizationActions.update).toHaveBeenCalledWith(detailedOrg);
+    expect(OrganizationActions.update).toHaveBeenCalledWith(detailedOrg, {replace: true});
     expect(OrganizationsActionCreator.setActiveOrganization).toHaveBeenCalled();
 
     expect(TeamStore.loadInitialData).toHaveBeenCalledWith(detailedOrg.teams);
@@ -82,7 +82,7 @@ describe('OrganizationActionCreator', function() {
       `/organizations/${lightOrg.slug}/teams/`,
       expect.anything()
     );
-    expect(OrganizationActions.update).toHaveBeenCalledWith(lightOrg);
+    expect(OrganizationActions.update).toHaveBeenCalledWith(lightOrg, {replace: true});
     expect(OrganizationsActionCreator.setActiveOrganization).toHaveBeenCalled();
 
     expect(TeamStore.loadInitialData).not.toHaveBeenCalled();
@@ -103,7 +103,7 @@ describe('OrganizationActionCreator', function() {
       `/organizations/${detailedOrg.slug}/`,
       expect.anything()
     );
-    expect(OrganizationActions.update).toHaveBeenCalledWith(detailedOrg);
+    expect(OrganizationActions.update).toHaveBeenCalledWith(detailedOrg, {replace: true});
     expect(OrganizationsActionCreator.setActiveOrganization).toHaveBeenCalled();
 
     expect(TeamStore.loadInitialData).toHaveBeenCalledWith(detailedOrg.teams);