diff options
author | philippe-perrin-sonarsource <philippe.perrin@sonarsource.com> | 2019-09-26 10:14:57 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-09-30 20:21:06 +0200 |
commit | 5cb50b88707b28319bd457a2602b6bfc7445f8d9 (patch) | |
tree | 0531a1dc8247b4a4d7bc0df4e61277067af3c7be /server/sonar-web | |
parent | 882c11382e0314743269de2fb1b1fde68fed8a83 (diff) | |
download | sonarqube-5cb50b88707b28319bd457a2602b6bfc7445f8d9.tar.gz sonarqube-5cb50b88707b28319bd457a2602b6bfc7445f8d9.zip |
SONAR-12413 Issue changelog contains confusing 'undefined' values
Diffstat (limited to 'server/sonar-web')
9 files changed, 151 insertions, 47 deletions
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/Item.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/Item.tsx index fd5379b7ded..40ef23bfec9 100644 --- a/server/sonar-web/src/main/js/apps/custom-measures/components/Item.tsx +++ b/server/sonar-web/src/main/js/apps/custom-measures/components/Item.tsx @@ -86,6 +86,7 @@ export default class Item extends React.PureComponent<Props, State> { render() { const { measure } = this.props; + const userName = measure.user.name || measure.user.login; return ( <tr data-metric={measure.metric.key}> @@ -117,8 +118,8 @@ export default class Item extends React.PureComponent<Props, State> { <MeasureDate measure={measure} /> {translate('by_')}{' '} <span className="js-custom-measure-user"> {isUserActive(measure.user) - ? measure.user.name || measure.user.login - : translateWithParameters('user.x_deleted', measure.user.login)} + ? userName + : translateWithParameters('user.x_deleted', userName)} </span> </td> diff --git a/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx b/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx index 6ed30634422..7e16ba09c35 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx @@ -161,11 +161,15 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> { handleAssigneeSearch = (query: string) => { return searchAssignees(query, this.state.organization).then(({ results }) => - results.map(r => ({ - avatar: r.avatar, - label: isUserActive(r) ? r.name : translateWithParameters('user.x_deleted', r.login), - value: r.login - })) + results.map(r => { + const userInfo = r.name || r.login; + + return { + avatar: r.avatar, + label: isUserActive(r) ? userInfo : translateWithParameters('user.x_deleted', userInfo), + value: r.login + }; + }) ); }; diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/BulkChangeModal-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/BulkChangeModal-test.tsx index 3803de3e021..60f6e8472d5 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/BulkChangeModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/BulkChangeModal-test.tsx @@ -33,6 +33,30 @@ jest.mock('../BulkChangeModal', () => { return mock; }); +jest.mock('../../utils', () => ({ + searchAssignees: jest.fn().mockResolvedValue({ + results: [ + { + active: true, + avatar: '##toto', + login: 'toto@toto', + name: 'toto' + }, + { + active: false, + avatar: '##toto', + login: 'login@login', + name: 'toto' + }, + { + active: true, + avatar: '##toto', + login: 'login@login' + } + ] + }) +})); + it('should display error message when no issues available', async () => { const wrapper = getWrapper([]); await waitAndUpdate(wrapper); @@ -57,8 +81,19 @@ it('should display warning when too many issues are passed', async () => { expect(wrapper.find('Alert')).toMatchSnapshot(); }); +it('should properly handle the search for assignee', async () => { + const issues: T.Issue[] = []; + for (let i = MAX_PAGE_SIZE + 1; i > 0; i--) { + issues.push(mockIssue()); + } + + const wrapper = getWrapper(issues); + const result = await wrapper.instance().handleAssigneeSearch('toto'); + expect(result).toMatchSnapshot(); +}); + const getWrapper = (issues: T.Issue[]) => { - return shallow( + return shallow<BulkChangeModal>( <BulkChangeModal component={undefined} currentUser={{ isLoggedIn: true }} diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap index 9b093ed6143..e8fb769f862 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap @@ -122,3 +122,23 @@ exports[`should display warning when too many issues are passed 2`] = ` /> </Alert> `; + +exports[`should properly handle the search for assignee 1`] = ` +Array [ + Object { + "avatar": "##toto", + "label": "toto", + "value": "toto@toto", + }, + Object { + "avatar": "##toto", + "label": "user.x_deleted.toto", + "value": "login@login", + }, + Object { + "avatar": "##toto", + "label": "user.x_deleted.login@login", + "value": "login@login", + }, +] +`; diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/AssigneeFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/AssigneeFacet.tsx index 76c93284a5f..3e7f242654e 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/AssigneeFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/AssigneeFacet.tsx @@ -104,18 +104,17 @@ export default class AssigneeFacet extends React.PureComponent<Props> { const user = this.props.referencedUsers[assignee]; - return user ? ( + if (!user) { + return assignee; + } + + const userName = user.name || user.login; + + return ( <> - <Avatar - className="little-spacer-right" - hash={user.avatar} - name={user.name || user.login} - size={16} - /> - {isUserActive(user) ? user.name : translateWithParameters('user.x_deleted', user.login)} + <Avatar className="little-spacer-right" hash={user.avatar} name={userName} size={16} /> + {isUserActive(user) ? userName : translateWithParameters('user.x_deleted', userName)} </> - ) : ( - assignee ); }; diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueAssign.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueAssign.tsx index a021c4bd7bc..e8f133b7484 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueAssign.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueAssign.tsx @@ -47,10 +47,9 @@ export default class IssueAssign extends React.PureComponent<Props> { renderAssignee() { const { issue } = this.props; - const assignee = - issue.assigneeActive !== false ? issue.assigneeName || issue.assignee : issue.assignee; + const assigneeName = issue.assigneeName || issue.assignee; - if (assignee) { + if (assigneeName) { return ( <> <span className="text-top"> @@ -63,8 +62,8 @@ export default class IssueAssign extends React.PureComponent<Props> { </span> <span className="issue-meta-label"> {issue.assigneeActive === false - ? translateWithParameters('user.x_deleted', assignee) - : assignee} + ? translateWithParameters('user.x_deleted', assigneeName) + : assigneeName} </span> </> ); diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueAssign-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueAssign-test.tsx index 1f59775d2fd..d6a42b5218f 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueAssign-test.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueAssign-test.tsx @@ -37,6 +37,10 @@ it('should render with the action', () => { expect(shallowRender()).toMatchSnapshot(); }); +it('should render a fallback assignee display if assignee info are not available', () => { + expect(shallowRender({ issue: { projectOrganization: 'org' } })).toMatchSnapshot(); +}); + it('should open the popup when the button is clicked', () => { const togglePopup = jest.fn(); const element = shallowRender({ togglePopup }); @@ -47,7 +51,7 @@ it('should open the popup when the button is clicked', () => { }); function shallowRender(props: Partial<IssueAssign['props']> = {}) { - return shallow( + return shallow<IssueAssign>( <IssueAssign canAssign={true} isOpen={false} diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap index 2e2ccd87499..324452de6ab 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap @@ -58,6 +58,42 @@ exports[`should open the popup when the button is clicked 2`] = ` </div> `; +exports[`should render a fallback assignee display if assignee info are not available 1`] = ` +<div + className="dropdown" +> + <Toggler + closeOnEscape={true} + onRequestClose={[Function]} + open={false} + overlay={ + <Connect(withCurrentUser(SetAssigneePopup)) + issue={ + Object { + "projectOrganization": "org", + } + } + onSelect={[MockFunction]} + /> + } + > + <ButtonLink + className="issue-action issue-action-with-options js-issue-assign" + onClick={[Function]} + > + <span + className="issue-meta-label" + > + unassigned + </span> + <DropdownIcon + className="little-spacer-left" + /> + </ButtonLink> + </Toggler> +</div> +`; + exports[`should render with the action 1`] = ` <div className="dropdown" diff --git a/server/sonar-web/src/main/js/components/issue/popups/ChangelogPopup.tsx b/server/sonar-web/src/main/js/components/issue/popups/ChangelogPopup.tsx index aacb92f283f..9557ac0e661 100644 --- a/server/sonar-web/src/main/js/components/issue/popups/ChangelogPopup.tsx +++ b/server/sonar-web/src/main/js/components/issue/popups/ChangelogPopup.tsx @@ -77,29 +77,35 @@ export default class ChangelogPopup extends React.PureComponent<Props, State> { </td> </tr> - {this.state.changelog.map((item, idx) => ( - <tr key={idx}> - <td className="thin text-left text-top nowrap"> - <DateTimeFormatter date={item.creationDate} /> - </td> - <td className="text-left text-top"> - <p> - <Avatar - className="little-spacer-right" - hash={item.avatar} - name={(item.isUserActive && item.userName) || item.user} - size={16} - /> - {item.isUserActive - ? item.userName || item.user - : translateWithParameters('user.x_deleted', item.user)} - </p> - {item.diffs.map(diff => ( - <IssueChangelogDiff diff={diff} key={diff.key} /> - ))} - </td> - </tr> - ))} + {this.state.changelog.map((item, idx) => { + const userName = item.userName || item.user; + + return ( + <tr key={idx}> + <td className="thin text-left text-top nowrap"> + <DateTimeFormatter date={item.creationDate} /> + </td> + <td className="text-left text-top"> + {userName && ( + <p> + <Avatar + className="little-spacer-right" + hash={item.avatar} + name={userName} + size={16} + /> + {item.isUserActive + ? userName + : translateWithParameters('user.x_deleted', userName)} + </p> + )} + {item.diffs.map(diff => ( + <IssueChangelogDiff diff={diff} key={diff.key} /> + ))} + </td> + </tr> + ); + })} </tbody> </table> </div> |