From aa04a150251c19c2b21348456a9a09abc88a1c59 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Tue, 2 Apr 2019 09:21:08 +0200 Subject: [PATCH] SONAR-11875 Connect Issues to the redux store, and refresh branch status when updating an issue --- .../main/js/apps/issues/components/App.tsx | 48 +++++++++++++------ .../issues/components/__tests__/App-test.tsx | 20 +++++++- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.tsx b/server/sonar-web/src/main/js/apps/issues/components/App.tsx index 6b71fd6ccb9..c1e1f55da27 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/App.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { connect } from 'react-redux'; import { FormattedMessage } from 'react-intl'; import * as key from 'keymaster'; import Helmet from 'react-helmet'; @@ -32,6 +33,18 @@ import PageActions from './PageActions'; import ConciseIssuesList from '../conciseIssuesList/ConciseIssuesList'; import ConciseIssuesListHeader from '../conciseIssuesList/ConciseIssuesListHeader'; import Sidebar from '../sidebar/Sidebar'; +import { Alert } from '../../../components/ui/Alert'; +import { Button } from '../../../components/ui/buttons'; +import Checkbox from '../../../components/controls/Checkbox'; +import DeferredSpinner from '../../../components/common/DeferredSpinner'; +import EmptySearch from '../../../components/common/EmptySearch'; +import FiltersHeader from '../../../components/common/FiltersHeader'; +import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; +import ListFooter from '../../../components/controls/ListFooter'; +import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; +import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; +import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; +import { withRouter, Location, Router } from '../../../components/hoc/withRouter'; import * as actions from '../actions'; import { areMyIssuesSelected, @@ -56,17 +69,6 @@ import { shouldOpenStandardsFacet, shouldOpenStandardsChildFacet } from '../utils'; -import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; -import { Alert } from '../../../components/ui/Alert'; -import { Button } from '../../../components/ui/buttons'; -import Checkbox from '../../../components/controls/Checkbox'; -import DeferredSpinner from '../../../components/common/DeferredSpinner'; -import EmptySearch from '../../../components/common/EmptySearch'; -import FiltersHeader from '../../../components/common/FiltersHeader'; -import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; -import ListFooter from '../../../components/controls/ListFooter'; -import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; -import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; import { isShortLivingBranch, isSameBranchLike, @@ -75,15 +77,15 @@ import { fillBranchLike } from '../../../helpers/branches'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { RawQuery } from '../../../helpers/query'; import { addSideBarClass, addWhitePageClass, removeSideBarClass, removeWhitePageClass } from '../../../helpers/pages'; +import { RawQuery } from '../../../helpers/query'; import { isSonarCloud } from '../../../helpers/system'; -import { withRouter, Location, Router } from '../../../components/hoc/withRouter'; +import { fetchBranchStatus } from '../../../store/rootActions'; import '../../../components/search-navigator.css'; import '../styles.css'; @@ -102,6 +104,7 @@ interface Props { branchLike?: T.BranchLike; component?: T.Component; currentUser: T.CurrentUser; + fetchBranchStatus: (branchLike: T.BranchLike, projectKey: string) => Promise; fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise; hideAuthorFacet?: boolean; location: Pick; @@ -785,6 +788,7 @@ export class App extends React.PureComponent { }; handleIssueChange = (issue: T.Issue) => { + this.refreshBranchStatus(); this.setState(state => ({ issues: state.issues.map(candidate => (candidate.key === issue.key ? issue : candidate)) })); @@ -802,12 +806,14 @@ export class App extends React.PureComponent { handleBulkChangeDone = () => { this.setState({ checkAll: false }); + this.refreshBranchStatus(); this.fetchFirstIssues(); this.handleCloseBulkChange(); }; handleReload = () => { this.fetchFirstIssues(); + this.refreshBranchStatus(); if (isShortLivingBranch(this.props.branchLike) || isPullRequest(this.props.branchLike)) { this.props.onBranchesChange(); } @@ -859,6 +865,13 @@ export class App extends React.PureComponent { this.setState(actions.selectPreviousFlow); }; + refreshBranchStatus = () => { + const { branchLike, component } = this.props; + if (branchLike && component && (isPullRequest(branchLike) || isShortLivingBranch(branchLike))) { + this.props.fetchBranchStatus(branchLike, component.key); + } + }; + renderBulkChange(openIssue: T.Issue | undefined) { const { component, currentUser } = this.props; const { checkAll, bulkChangeModal, checked, issues, paging } = this.state; @@ -1148,4 +1161,11 @@ export class App extends React.PureComponent { } } -export default withRouter(App); +const mapDispatchToProps = { fetchBranchStatus: fetchBranchStatus as any }; + +export default withRouter( + connect( + null, + mapDispatchToProps + )(App) +); diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx index 10e534e364f..7647847d84c 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx @@ -29,7 +29,6 @@ import { mockCurrentUser } from '../../../../helpers/testMocks'; import handleRequiredAuthentication from '../../../../app/utils/handleRequiredAuthentication'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; import { enableLocationsNavigator, selectNextLocation, @@ -37,11 +36,29 @@ import { selectNextFlow, selectPreviousFlow } from '../../actions'; +import { waitAndUpdate, KEYCODE_MAP, keydown } from '../../../../helpers/testUtils'; jest.mock('../../../../app/utils/handleRequiredAuthentication', () => ({ default: jest.fn() })); +jest.mock('keymaster', () => { + const key: any = (bindKey: string, _: string, callback: Function) => { + document.addEventListener('keydown', (event: KeyboardEvent) => { + if (bindKey.split(',').includes(KEYCODE_MAP[event.keyCode])) { + return callback(); + } + return true; + }); + }; + + key.getScope = () => 'issues'; + key.setScope = () => {}; + key.deleteScope = () => {}; + + return key; +}); + const ISSUES = [ { key: 'foo' } as T.Issue, { key: 'bar' } as T.Issue, @@ -339,6 +356,7 @@ function shallowRender(props: Partial = {}) { qualifier: 'Doe' }} currentUser={mockLoggedInUser()} + fetchBranchStatus={jest.fn()} fetchIssues={jest.fn().mockResolvedValue({ components: [referencedComponent], effortTotal: 1, -- 2.39.5