From: Grégoire Aubert Date: Thu, 17 Aug 2017 07:10:21 +0000 (+0200) Subject: SONAR-9435 Manage issues' popup at the list level X-Git-Tag: 6.6-RC1~573 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=57fe683ca1f0cc1307a601e44ae367159257a66d;p=sonarqube.git SONAR-9435 Manage issues' popup at the list level --- 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 56ef5e68d13..199c814ce92 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 @@ -86,6 +86,10 @@ export type State = { myIssues: boolean, openFacets: { [string]: boolean }, openIssue: ?Issue, + openPopup: ?{ + issue: string, + name: string + }, paging?: Paging, query: Query, referencedComponents: { [string]: ReferencedComponent }, @@ -117,6 +121,7 @@ export default class App extends React.PureComponent { myIssues: areMyIssuesSelected(props.location.query), openFacets: { resolutions: true, types: true }, openIssue: null, + openPopup: null, query: parseQuery(props.location.query), referencedComponents: {}, referencedLanguages: {}, @@ -594,6 +599,19 @@ export default class App extends React.PureComponent { }); }; + handlePopupToggle = (issue /*: string */, popupName /*: string */, open /*: ?boolean */) => { + this.setState((state /*: State */) => { + const samePopup = + state.openPopup && state.openPopup.name === popupName && state.openPopup.issue === issue; + if (open !== false && !samePopup) { + return { openPopup: { issue, name: popupName } }; + } else if (open !== true && samePopup) { + return { openPopup: null }; + } + return state; + }); + }; + handleIssueCheck = (issue /*: string */) => { this.setState(state => ({ checked: state.checked.includes(issue) @@ -792,6 +810,8 @@ export default class App extends React.PureComponent { onIssueChange={this.handleIssueChange} onIssueCheck={currentUser.isLoggedIn ? this.handleIssueCheck : undefined} onIssueClick={this.openIssue} + onPopupToggle={this.handlePopupToggle} + openPopup={this.state.openPopup} organization={organization} selectedIssue={selectedIssue} />} diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.js b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.js index 88ba739aa8b..8a405e94c33 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.js +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.js @@ -32,6 +32,8 @@ type Props = {| onIssueChange: Issue => void, onIssueCheck?: string => void, onIssueClick: string => void, + onPopupToggle: (issue: string, popupName: string, open: ?boolean ) => void, + openPopup: ?{ issue: string, name: string}, organization?: { key: string }, selectedIssue: ?Issue |}; @@ -41,7 +43,7 @@ export default class IssuesList extends React.PureComponent { /*:: props: Props; */ render() { - const { checked, component, issues, selectedIssue } = this.props; + const { checked, component, issues, openPopup, selectedIssue } = this.props; return (
@@ -55,6 +57,8 @@ export default class IssuesList extends React.PureComponent { onCheck={this.props.onIssueCheck} onClick={this.props.onIssueClick} onFilterChange={this.props.onFilterChange} + onPopupToggle={this.props.onPopupToggle} + openPopup={openPopup && openPopup.issue === issue.key ? openPopup.name : null} organization={this.props.organization} previousIssue={index > 0 ? issues[index - 1] : null} selected={selectedIssue != null && selectedIssue.key === issue.key} diff --git a/server/sonar-web/src/main/js/apps/issues/components/ListItem.js b/server/sonar-web/src/main/js/apps/issues/components/ListItem.js index 7189ff6f84a..20bd23dfb9f 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/ListItem.js +++ b/server/sonar-web/src/main/js/apps/issues/components/ListItem.js @@ -33,6 +33,8 @@ type Props = {| onCheck?: string => void, onClick: string => void, onFilterChange: (changes: {}) => void, + onPopupToggle: (issue: string, popupName: string, open: ?boolean ) => void, + openPopup: ?string, organization?: { key: string }, previousIssue: ?Object, selected: boolean @@ -107,6 +109,8 @@ export default class ListItem extends React.PureComponent { onCheck={this.props.onCheck} onClick={this.props.onClick} onFilter={this.handleFilter} + onPopupToggle={this.props.onPopupToggle} + openPopup={this.props.openPopup} selected={this.props.selected} />
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js index 217e33ce936..d656865b0ec 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.js @@ -99,6 +99,10 @@ type State = { notAccessible: boolean, notExist: boolean, openIssuesByLine: { [number]: boolean }, + openPopup: ?{ + issue: string, + name: string + }, selectedIssue?: string, sources?: Array, sourceRemoved: boolean, @@ -151,6 +155,7 @@ export default class SourceViewerBase extends React.PureComponent { notAccessible: false, notExist: false, openIssuesByLine: {}, + openPopup: null, selectedIssue: props.selectedIssue, selectedIssueLocation: null, sourceRemoved: false, @@ -470,6 +475,19 @@ export default class SourceViewerBase extends React.PureComponent { } }; + handlePopupToggle = (issue /*: string */, popupName /*: string */, open /*: ?boolean */) => { + this.setState((state /*: State */) => { + const samePopup = + state.openPopup && state.openPopup.name === popupName && state.openPopup.issue === issue; + if (open !== false && !samePopup) { + return { openPopup: { issue, name: popupName } }; + } else if (open !== true && samePopup) { + return { openPopup: null }; + } + return state; + }); + }; + displayLinePopup(line /*: number */, element /*: HTMLElement */) { const popup = new LineActionsPopupView({ line, @@ -571,6 +589,8 @@ export default class SourceViewerBase extends React.PureComponent { onIssuesClose={this.handleCloseIssues} onLineClick={this.handleLineClick} onLocationSelect={this.props.onLocationSelect} + onPopupToggle={this.handlePopupToggle} + openPopup={this.state.openPopup} onSCMClick={this.handleSCMClick} onSymbolClick={this.handleSymbolClick} openIssuesByLine={this.state.openIssuesByLine} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js index b739c6b0840..f928aa8f5fc 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.js @@ -68,6 +68,8 @@ export default class SourceViewerCode extends React.PureComponent { onSCMClick: (SourceLine, HTMLElement) => void, onSymbolClick: (Array) => void, openIssuesByLine: { [number]: boolean }, + onPopupToggle: (issue: string, popupName: string, open: ?boolean ) => void, + openPopup: ?{ issue: string, name: string}, scroll?: HTMLElement => void, selectedIssue: string | null, sources: Array, @@ -174,6 +176,8 @@ export default class SourceViewerCode extends React.PureComponent { onSCMClick={this.props.onSCMClick} onSymbolClick={this.props.onSymbolClick} openIssues={this.props.openIssuesByLine[line.line] || false} + onPopupToggle={this.props.onPopupToggle} + openPopup={this.props.openPopup} previousLine={index > 0 ? sources[index - 1] : undefined} scroll={this.props.scroll} secondaryIssueLocations={optimizedSecondaryIssueLocations} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.js b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.js index 0e66fbbe50a..10f84254b27 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.js +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.js @@ -62,6 +62,8 @@ type Props = {| onSCMClick: (SourceLine, HTMLElement) => void, onSymbolClick: (Array) => void, openIssues: boolean, + onPopupToggle: (issue: string, popupName: string, open: ?boolean ) => void, + openPopup: ?{ issue: string, name: string}, previousLine?: SourceLine, scroll?: HTMLElement => void, secondaryIssueLocations: Array<{ @@ -150,6 +152,8 @@ export default class Line extends React.PureComponent { onIssueSelect={this.props.onIssueSelect} onLocationSelect={this.props.onLocationSelect} onSymbolClick={this.props.onSymbolClick} + onPopupToggle={this.props.onPopupToggle} + openPopup={this.props.openPopup} scroll={this.props.scroll} secondaryIssueLocations={this.props.secondaryIssueLocations} selectedIssue={this.props.selectedIssue} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.js b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.js index e9493b8a8b9..e2bd3eaeb7e 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.js +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.js @@ -40,6 +40,8 @@ type Props = {| onIssueSelect: (issueKey: string) => void, onLocationSelect?: number => void, onSymbolClick: (Array) => void, + onPopupToggle: (issue: string, popupName: string, open: ?boolean ) => void, + openPopup: ?{ issue: string, name: string}, scroll?: HTMLElement => void, secondaryIssueLocations: Array<{ from: number, @@ -221,6 +223,8 @@ export default class LineCode extends React.PureComponent { issues={issues} onIssueChange={this.props.onIssueChange} onIssueClick={onIssueSelect} + onPopupToggle={this.props.onPopupToggle} + openPopup={this.props.openPopup} selectedIssue={selectedIssue} />} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.js b/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.js index 7a498665fae..470cf536cea 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.js +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.js @@ -27,6 +27,8 @@ type Props = { issues: Array, onIssueChange: IssueType => void, onIssueClick: (issueKey: string) => void, + onPopupToggle: (issue: string, popupName: string, open: ?boolean ) => void, + openPopup: ?{ issue: string, name: string}, selectedIssue: string | null }; */ @@ -35,7 +37,7 @@ export default class LineIssuesList extends React.PureComponent { /*:: props: Props; */ render() { - const { issues, onIssueClick, selectedIssue } = this.props; + const { issues, onIssueClick, openPopup, selectedIssue } = this.props; return (
@@ -45,6 +47,8 @@ export default class LineIssuesList extends React.PureComponent { key={issue.key} onChange={this.props.onIssueChange} onClick={onIssueClick} + onPopupToggle={this.props.onPopupToggle} + openPopup={openPopup && openPopup.issue === issue.key ? openPopup.name : null} selected={selectedIssue === issue.key} /> )} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.js b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.js index 8a157714eec..3366bc8c868 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.js +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.js @@ -36,6 +36,8 @@ it('render code', () => { onIssueSelect={jest.fn()} onSelectLocation={jest.fn()} onSymbolClick={jest.fn()} + onPopupToggle={jest.fn()} + openPopup={null} selectedIssue="issue-1" showIssues={true} /> diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.js b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.js index a9a0e0763b9..841f1e9b7e3 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.js +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.js @@ -26,7 +26,14 @@ it('render issues list', () => { const issues = [{ key: 'foo' }, { key: 'bar' }]; const onIssueClick = jest.fn(); const wrapper = shallow( - + ); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.js.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.js.snap index 70d86769f06..9f6da3b0b4b 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.js.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.js.snap @@ -43,6 +43,8 @@ exports[`render code 1`] = ` ] } onIssueClick={[Function]} + onPopupToggle={[Function]} + openPopup={null} selectedIssue="issue-1" /> diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesList-test.js.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesList-test.js.snap index 9008da985b0..f31277211ba 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesList-test.js.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesList-test.js.snap @@ -11,6 +11,8 @@ exports[`render issues list 1`] = ` } } onClick={[Function]} + onPopupToggle={[Function]} + openPopup={null} selected={true} />
diff --git a/server/sonar-web/src/main/js/components/issue/Issue.js b/server/sonar-web/src/main/js/components/issue/Issue.js index 674d9c35216..70a1fc90746 100644 --- a/server/sonar-web/src/main/js/components/issue/Issue.js +++ b/server/sonar-web/src/main/js/components/issue/Issue.js @@ -35,20 +35,14 @@ type Props = {| onCheck?: string => void, onClick: string => void, onFilter?: (property: string, issue: Issue) => void, + onPopupToggle: (issue: string, popupName: string, open: ?boolean ) => void, + openPopup: ?string, selected: boolean |}; */ -/*:: -type State = { - currentPopup: string -}; -*/ - export default class BaseIssue extends React.PureComponent { - /*:: mounted: boolean; */ /*:: props: Props; */ - /*:: state: State; */ static contextTypes = { store: PropTypes.object @@ -58,15 +52,7 @@ export default class BaseIssue extends React.PureComponent { selected: false }; - constructor(props /*: Props */) { - super(props); - this.state = { - currentPopup: '' - }; - } - componentDidMount() { - this.mounted = true; if (this.props.selected) { this.bindShortcuts(); } @@ -85,7 +71,6 @@ export default class BaseIssue extends React.PureComponent { } componentWillUnmount() { - this.mounted = false; if (this.props.selected) { this.unbindShortcuts(); } @@ -135,16 +120,7 @@ export default class BaseIssue extends React.PureComponent { } togglePopup = (popupName /*: string */, open /*: ?boolean */) => { - if (this.mounted) { - this.setState((prevState /*: State */) => { - if (prevState.currentPopup !== popupName && open !== false) { - return { currentPopup: popupName }; - } else if (prevState.currentPopup === popupName && open !== true) { - return { currentPopup: '' }; - } - return prevState; - }); - } + this.props.onPopupToggle(this.props.issue.key, popupName, open); }; handleAssignement = (login /*: string */) => { @@ -175,7 +151,7 @@ export default class BaseIssue extends React.PureComponent { onFilter={this.props.onFilter} onChange={this.props.onChange} togglePopup={this.togglePopup} - currentPopup={this.state.currentPopup} + currentPopup={this.props.openPopup} selected={this.props.selected} /> ); diff --git a/server/sonar-web/src/main/js/components/issue/IssueView.js b/server/sonar-web/src/main/js/components/issue/IssueView.js index be2f91af2ef..5800390c44c 100644 --- a/server/sonar-web/src/main/js/components/issue/IssueView.js +++ b/server/sonar-web/src/main/js/components/issue/IssueView.js @@ -30,7 +30,7 @@ import { deleteIssueComment, editIssueComment } from '../../api/issues'; /*:: type Props = {| checked?: boolean, - currentPopup: string, + currentPopup: ?string, issue: Issue, onAssign: string => void, onChange: Issue => void, diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.js b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.js index 2c119cbb393..03837602471 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.js +++ b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.js @@ -32,7 +32,7 @@ import { translate, translateWithParameters } from '../../../helpers/l10n'; /*:: type Props = { issue: Issue, - currentPopup: string, + currentPopup: ?string, onAssign: string => void, onChange: Issue => void, onFail: Error => void, diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueCommentAction.js b/server/sonar-web/src/main/js/components/issue/components/IssueCommentAction.js index 718d456df06..b756e185da9 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueCommentAction.js +++ b/server/sonar-web/src/main/js/components/issue/components/IssueCommentAction.js @@ -29,7 +29,7 @@ import { translate } from '../../../helpers/l10n'; /*:: type Props = {| commentPlaceholder: string, - currentPopup: string, + currentPopup: ?string, issueKey: string, onChange: Issue => void, onFail: Error => void, diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js index c6958f6a768..82aa5852ef4 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js +++ b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js @@ -34,7 +34,7 @@ import { translate, translateWithParameters } from '../../../helpers/l10n'; /*:: type Props = {| issue: Issue, - currentPopup: string, + currentPopup: ?string, onFail: Error => void, onFilter?: (property: string, issue: Issue) => void, togglePopup: (string, boolean | void) => void diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueCommentAction-test.js b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueCommentAction-test.js index c8ac8e05986..4485f3edea0 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueCommentAction-test.js +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueCommentAction-test.js @@ -26,7 +26,7 @@ it('should render correctly', () => { const element = shallow( { const element = shallow( { const element = shallow( - + ); expect(element).toMatchSnapshot(); }); @@ -50,7 +50,7 @@ it('should render the titlebar with the filter', () => { const element = shallow(