]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9605 Fix infinite spinner on issues page
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Wed, 16 Aug 2017 10:39:05 +0000 (12:39 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Thu, 17 Aug 2017 14:42:34 +0000 (16:42 +0200)
server/sonar-web/src/main/js/apps/issues/components/App.js
server/sonar-web/src/main/js/apps/issues/components/AppContainer.js
server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.js
server/sonar-web/src/main/js/apps/issues/components/IssuesCounter.js
server/sonar-web/src/main/js/apps/issues/components/PageActions.js
server/sonar-web/src/main/js/apps/issues/styles.css

index 06746b240b097bbbc642075e1692b22f6719ff4b..56ef5e68d133d9207802ac413a66836dad8d28c1 100644 (file)
@@ -67,7 +67,6 @@ export type Props = {
   currentUser: CurrentUser,
   fetchIssues: (query: RawQuery) => Promise<*>,
   location: { pathname: string, query: RawQuery },
-  onRequestFail: Error => void,
   organization?: { key: string },
   router: {
     push: ({ pathname: string, query?: RawQuery }) => void,
@@ -381,27 +380,35 @@ export default class App extends React.PureComponent {
 
   fetchFirstIssues() {
     this.setState({ checked: [], loading: true });
-    return this.fetchIssues({}, true).then(({ facets, issues, paging, ...other }) => {
-      if (this.mounted) {
-        const openIssue = this.getOpenIssue(this.props, issues);
-        this.setState({
-          facets: parseFacets(facets),
-          loading: false,
-          issues,
-          openIssue,
-          paging,
-          referencedComponents: keyBy(other.components, 'uuid'),
-          referencedLanguages: keyBy(other.languages, 'key'),
-          referencedRules: keyBy(other.rules, 'key'),
-          referencedUsers: keyBy(other.users, 'login'),
-          selected:
-            issues.length > 0 ? (openIssue != null ? openIssue.key : issues[0].key) : undefined,
-          selectedFlowIndex: null,
-          selectedLocationIndex: null
-        });
+    return this.fetchIssues({}, true).then(
+      ({ facets, issues, paging, ...other }) => {
+        if (this.mounted) {
+          const openIssue = this.getOpenIssue(this.props, issues);
+          this.setState({
+            facets: parseFacets(facets),
+            loading: false,
+            issues,
+            openIssue,
+            paging,
+            referencedComponents: keyBy(other.components, 'uuid'),
+            referencedLanguages: keyBy(other.languages, 'key'),
+            referencedRules: keyBy(other.rules, 'key'),
+            referencedUsers: keyBy(other.users, 'login'),
+            selected:
+              issues.length > 0 ? (openIssue != null ? openIssue.key : issues[0].key) : undefined,
+            selectedFlowIndex: null,
+            selectedLocationIndex: null
+          });
+        }
+        return issues;
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+        return Promise.reject();
       }
-      return issues;
-    });
+    );
   }
 
   fetchIssuesPage = (p /*: number */) => {
@@ -433,15 +440,22 @@ export default class App extends React.PureComponent {
     const p = paging.pageIndex + 1;
 
     this.setState({ loading: true });
-    this.fetchIssuesPage(p).then(response => {
-      if (this.mounted) {
-        this.setState(state => ({
-          loading: false,
-          issues: [...state.issues, ...response.issues],
-          paging: response.paging
-        }));
+    this.fetchIssuesPage(p).then(
+      response => {
+        if (this.mounted) {
+          this.setState(state => ({
+            loading: false,
+            issues: [...state.issues, ...response.issues],
+            paging: response.paging
+          }));
+        }
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
       }
-    });
+    );
   };
 
   fetchIssuesForComponent = (component /*: string */, from /*: number */, to /*: number */) => {
@@ -469,16 +483,24 @@ export default class App extends React.PureComponent {
     }
 
     this.setState({ loading: true });
-    return this.fetchIssuesUntil(paging.pageIndex + 1, done).then(response => {
-      const nextIssues = [...issues, ...response.issues];
-
-      this.setState({
-        issues: nextIssues,
-        loading: false,
-        paging: response.paging
-      });
-      return nextIssues.filter(isSameComponent);
-    });
+    return this.fetchIssuesUntil(paging.pageIndex + 1, done).then(
+      response => {
+        const nextIssues = [...issues, ...response.issues];
+        if (this.mounted) {
+          this.setState({
+            issues: nextIssues,
+            loading: false,
+            paging: response.paging
+          });
+        }
+        return nextIssues.filter(isSameComponent);
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+      }
+    );
   };
 
   fetchFacet = (facet /*: string */) => {
@@ -671,7 +693,6 @@ export default class App extends React.PureComponent {
             fetchIssues={bulkChange === 'all' ? this.fetchIssues : this.getCheckedIssues}
             onClose={this.closeBulkChange}
             onDone={this.handleBulkChangeDone}
-            onRequestFail={this.props.onRequestFail}
             organization={this.props.organization}
           />}
       </div>
index 9d1ab75594b1c86e29d334995db707db377d002f..040f0c4718f46afe52f4e2701b4970b320a76f09 100644 (file)
@@ -23,7 +23,7 @@ import { withRouter } from 'react-router';
 /*:: import type { Dispatch } from 'redux'; */
 import { uniq } from 'lodash';
 import App from './App';
-import { onFail } from '../../../store/rootActions';
+import throwGlobalError from '../../../app/utils/throwGlobalError';
 import { getComponent, getCurrentUser } from '../../../store/rootReducer';
 import { getOrganizations } from '../../../api/organizations';
 import { receiveOrganizations } from '../../../store/organizations/duck';
@@ -46,7 +46,7 @@ const fetchIssueOrganizations = issues => dispatch => {
   const organizationKeys = uniq(issues.map(issue => issue.organization));
   return getOrganizations(organizationKeys).then(
     response => dispatch(receiveOrganizations(response.organizations)),
-    onFail(dispatch)
+    throwGlobalError
   );
 };
 
@@ -59,11 +59,8 @@ const fetchIssues = (query /*: RawQuery */) => dispatch =>
       return { ...response, issues: parsedIssues };
     })
     .then(response => dispatch(fetchIssueOrganizations(response.issues)).then(() => response))
-    .catch(onFail(dispatch));
+    .catch(throwGlobalError);
 
-const onRequestFail = (error /*: Error */) => (dispatch /*: Dispatch<*> */) =>
-  onFail(dispatch)(error);
-
-const mapDispatchToProps = { fetchIssues, onRequestFail };
+const mapDispatchToProps = { fetchIssues };
 
 export default connect(mapStateToProps, mapDispatchToProps)(withRouter(App));
index e5002e86251797453c13a0bc11dcb33c9216e857..a0ce352c35ba4091fc9348871443e90bec8b6a75 100644 (file)
@@ -29,6 +29,7 @@ import MarkdownTips from '../../../components/common/MarkdownTips';
 import SeverityHelper from '../../../components/shared/SeverityHelper';
 import Avatar from '../../../components/ui/Avatar';
 import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
+import throwGlobalError from '../../../app/utils/throwGlobalError';
 import { searchIssueTags, bulkChangeIssues } from '../../../api/issues';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { searchAssignees } from '../utils';
@@ -42,7 +43,6 @@ type Props = {|
   fetchIssues: ({}) => Promise<*>,
   onClose: () => void,
   onDone: () => void,
-  onRequestFail: Error => void,
   organization?: { key: string }
 |};
 */
@@ -200,7 +200,7 @@ export default class BulkChangeModal extends React.PureComponent {
       },
       (error /*: Error */) => {
         this.setState({ submitting: false });
-        this.props.onRequestFail(error);
+        throwGlobalError(error);
       }
     );
   };
index 7d43bccc987937344e474234f3aadfd25760c602..b44ed55d61a57c2436377d165ed8101929e91f95 100644 (file)
@@ -24,13 +24,14 @@ import { formatMeasure } from '../../../helpers/measures';
 
 /*::
 type Props = {
+  className? : string,
   current: ?number,
   total: number
 };
 */
 
 const IssuesCounter = (props /*: Props */) =>
-  <span>
+  <span className={props.className}>
     <strong>
       {props.current != null &&
         <span>
index 64366643914cc40788b9df8e3c1115f70babf5b6..199a627d929e07d7912d16e219256e073285fd4b 100644 (file)
@@ -19,6 +19,7 @@
  */
 // @flow
 import React from 'react';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
 import IssuesCounter from './IssuesCounter';
 import ReloadButton from './ReloadButton';
 /*:: import type { Paging } from '../utils'; */
@@ -62,10 +63,11 @@ export default class PageActions extends React.PureComponent {
         {this.renderShortcuts()}
 
         <div className="issues-page-actions">
-          {this.props.loading
-            ? <i className="issues-main-header-spinner spinner spacer-right" />
-            : <ReloadButton className="spacer-right" onClick={this.props.onReload} />}
-          {paging != null && <IssuesCounter current={selectedIndex} total={paging.total} />}
+          <DeferredSpinner className="issues-main-header-spinner" loading={this.props.loading}>
+            <ReloadButton onClick={this.props.onReload} />
+          </DeferredSpinner>
+          {paging != null &&
+            <IssuesCounter className="spacer-left" current={selectedIndex} total={paging.total} />}
         </div>
       </div>
     );
index bb0f41ec65ba4fa70d59e8bf06458c291dccc310..dee7d080b352121728b20627509ef37da489da75 100644 (file)
@@ -2,6 +2,10 @@
   line-height: 24px;
 }
 
+.issues-main-header-spinner {
+  margin-right: 2px;
+}
+
 .concise-issues-list-header,
 .concise-issues-list-header-inner {
 }