diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-08-16 12:39:05 +0200 |
---|---|---|
committer | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-08-17 16:42:34 +0200 |
commit | eae2f0df4ef757c0c8fb92e49e808e5feace167b (patch) | |
tree | 15955463850ea50f510a944b02e3e5f9e0d4e8dc /server/sonar-web/src/main | |
parent | 8f016916eafb2052e472ba8eba78d0e7db4b5210 (diff) | |
download | sonarqube-eae2f0df4ef757c0c8fb92e49e808e5feace167b.tar.gz sonarqube-eae2f0df4ef757c0c8fb92e49e808e5feace167b.zip |
SONAR-9605 Fix infinite spinner on issues page
Diffstat (limited to 'server/sonar-web/src/main')
6 files changed, 79 insertions, 54 deletions
diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.js b/server/sonar-web/src/main/js/apps/issues/components/App.js index 06746b240b0..56ef5e68d13 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/App.js +++ b/server/sonar-web/src/main/js/apps/issues/components/App.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/issues/components/AppContainer.js b/server/sonar-web/src/main/js/apps/issues/components/AppContainer.js index 9d1ab75594b..040f0c4718f 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/AppContainer.js +++ b/server/sonar-web/src/main/js/apps/issues/components/AppContainer.js @@ -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)); diff --git a/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.js b/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.js index e5002e86251..a0ce352c35b 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.js +++ b/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.js @@ -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); } ); }; diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesCounter.js b/server/sonar-web/src/main/js/apps/issues/components/IssuesCounter.js index 7d43bccc987..b44ed55d61a 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesCounter.js +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesCounter.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/issues/components/PageActions.js b/server/sonar-web/src/main/js/apps/issues/components/PageActions.js index 64366643914..199a627d929 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/PageActions.js +++ b/server/sonar-web/src/main/js/apps/issues/components/PageActions.js @@ -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> ); diff --git a/server/sonar-web/src/main/js/apps/issues/styles.css b/server/sonar-web/src/main/js/apps/issues/styles.css index bb0f41ec65b..dee7d080b35 100644 --- a/server/sonar-web/src/main/js/apps/issues/styles.css +++ b/server/sonar-web/src/main/js/apps/issues/styles.css @@ -2,6 +2,10 @@ line-height: 24px; } +.issues-main-header-spinner { + margin-right: 2px; +} + .concise-issues-list-header, .concise-issues-list-header-inner { } |