]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12413 Issue changelog contains confusing 'undefined' values
authorphilippe-perrin-sonarsource <philippe.perrin@sonarsource.com>
Thu, 26 Sep 2019 08:14:57 +0000 (10:14 +0200)
committerSonarTech <sonartech@sonarsource.com>
Mon, 30 Sep 2019 18:21:06 +0000 (20:21 +0200)
server/sonar-web/src/main/js/apps/custom-measures/components/Item.tsx
server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx
server/sonar-web/src/main/js/apps/issues/components/__tests__/BulkChangeModal-test.tsx
server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap
server/sonar-web/src/main/js/apps/issues/sidebar/AssigneeFacet.tsx
server/sonar-web/src/main/js/components/issue/components/IssueAssign.tsx
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueAssign-test.tsx
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap
server/sonar-web/src/main/js/components/issue/popups/ChangelogPopup.tsx

index fd5379b7dedf519a871afa03a00be2393d421c4c..40ef23bfec925e83d8e85b89a38c4ea7f544647a 100644 (file)
@@ -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>
 
index 6ed30634422f68b706caf4e27dd15427e9919186..7e16ba09c35501c083026a82b006fcf5410873c5 100644 (file)
@@ -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
+        };
+      })
     );
   };
 
index 3803de3e021a39a193a321b37dd548107b0f9aba..60f6e8472d5ac909511772b8c4c075c9e2e45456 100644 (file)
@@ -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 }}
index 9b093ed614342da61a91e279bfb69539d9e045a5..e8fb769f86266a2189ba657abe7f81007d0162bc 100644 (file)
@@ -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",
+  },
+]
+`;
index 76c93284a5fb73cc93877c428ee818c9f82bacd4..3e7f242654e9c10cb2f5d91ee38dc970a5a88a71 100644 (file)
@@ -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
     );
   };
 
index a021c4bd7bc55f46e5d702e859320dc1d7801b3a..e8f133b74848e88b6a31fff967cc54fea470f74c 100644 (file)
@@ -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>
         </>
       );
index 1f59775d2fd7ae41d44d91fe91706c7bde75e60b..d6a42b5218fed6515991f7216a9838fa8446689e 100644 (file)
@@ -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}
index 2e2ccd87499da594daec37ad0edeeca7ca506e70..324452de6ab00e4d8307d1d1b168dcc26ad510d9 100644 (file)
@@ -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"
index aacb92f283f0ebd8a008df16416eb5a760d63809..9557ac0e661a56c1621d3e430ad27afc8fb43058 100644 (file)
@@ -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>