]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12720 Review tab displays hotspot's comments
authorPhilippe Perrin <philippe.perrin@sonarsource.com>
Fri, 20 Dec 2019 10:26:12 +0000 (11:26 +0100)
committerSonarTech <sonartech@sonarsource.com>
Mon, 13 Jan 2020 19:46:32 +0000 (20:46 +0100)
16 files changed:
server/sonar-web/src/main/js/api/security-hotspots.ts
server/sonar-web/src/main/js/apps/securityHotspots/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotViewerRenderer.tsx
server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotViewerReviewHistoryTab.tsx
server/sonar-web/src/main/js/apps/securityHotspots/components/HotspotViewerTabs.tsx
server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotViewerRenderer-test.tsx
server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/HotspotViewerReviewHistoryTab-test.tsx
server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotSnippetContainer-test.tsx.snap
server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap
server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap
server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotViewerReviewHistoryTab-test.tsx.snap
server/sonar-web/src/main/js/apps/securityHotspots/components/__tests__/__snapshots__/HotspotViewerTabs-test.tsx.snap
server/sonar-web/src/main/js/apps/securityHotspots/utils.ts
server/sonar-web/src/main/js/helpers/mocks/security-hotspots.ts
server/sonar-web/src/main/js/types/security-hotspots.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index cd4bd24150a43c0735984f8329ba81e019bcde63..78ec7567f62385a1e2232bc73928acdda409ee1f 100644 (file)
@@ -59,5 +59,27 @@ export function getSecurityHotspots(
 }
 
 export function getSecurityHotspotDetails(securityHotspotKey: string): Promise<Hotspot> {
-  return getJSON('/api/hotspots/show', { hotspot: securityHotspotKey }).catch(throwGlobalError);
+  return getJSON('/api/hotspots/show', { hotspot: securityHotspotKey })
+    .then((response: Hotspot & { users: T.UserBase[] }) => {
+      const { users, ...hotspot } = response;
+
+      if (users) {
+        if (hotspot.assignee) {
+          hotspot.assigneeUser = users.find(u => u.login === hotspot.assignee) || {
+            active: true,
+            login: hotspot.assignee
+          };
+        }
+        hotspot.authorUser = users.find(u => u.login === hotspot.author) || {
+          active: true,
+          login: hotspot.author
+        };
+        hotspot.comment.forEach(c => {
+          c.user = users.find(u => u.login === c.login) || { active: true, login: c.login };
+        });
+      }
+
+      return hotspot;
+    })
+    .catch(throwGlobalError);
 }
index cf0ec56d0d7b98fb3bbc0206e23699a52b896b28..c93b4e01bcbfd5aa913b419735e7010f55940880 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { mockHotspot, mockRawHotspot } from '../../../helpers/mocks/security-hotspots';
+import { mockUser } from '../../../helpers/testMocks';
 import { ReviewHistoryType, RiskExposure } from '../../../types/security-hotspots';
 import { getHotspotReviewHistory, groupByCategory, mapRules, sortHotspots } from '../utils';
 
@@ -158,25 +159,56 @@ describe('getHotspotReviewHistory', () => {
         }
       ]
     };
+    const commentElement = {
+      key: 'comment-1',
+      createdAt: '2018-09-10',
+      htmlText: '<strong>TEST</strong>',
+      markdown: '*TEST*',
+      updatable: true,
+      login: 'dude-1',
+      user: mockUser({ login: 'dude-1' })
+    };
+    const commentElement1 = {
+      key: 'comment-2',
+      createdAt: '2018-09-11',
+      htmlText: '<strong>TEST</strong>',
+      markdown: '*TEST*',
+      updatable: true,
+      login: 'dude-2',
+      user: mockUser({ login: 'dude-2' })
+    };
     const hotspot = mockHotspot({
       creationDate: '2018-09-01',
-      changelog: [changelogElement]
+      changelog: [changelogElement],
+      comment: [commentElement, commentElement1]
     });
     const history = getHotspotReviewHistory(hotspot);
 
-    expect(history.length).toBe(2);
+    expect(history.length).toBe(4);
     expect(history[0]).toEqual(
       expect.objectContaining({
         type: ReviewHistoryType.Creation,
         date: hotspot.creationDate,
-        user: {
-          avatar: hotspot.author.avatar,
-          name: hotspot.author.name,
-          active: hotspot.author.active
-        }
+        user: hotspot.authorUser
       })
     );
     expect(history[1]).toEqual(
+      expect.objectContaining({
+        type: ReviewHistoryType.Comment,
+        date: commentElement.createdAt,
+        user: commentElement.user,
+        html: commentElement.htmlText
+      })
+    );
+    expect(history[2]).toEqual(
+      expect.objectContaining({
+        type: ReviewHistoryType.Comment,
+        date: commentElement1.createdAt,
+        user: commentElement1.user,
+        html: commentElement1.htmlText
+      })
+    );
+    expect(history[3]).toEqual(
       expect.objectContaining({
         type: ReviewHistoryType.Diff,
         date: changelogElement.creationDate,
index cf2b54a7e6e4bd011b1f83b03f51fb6f98be6f46..34078e7b123d8d94faa9c063e7a8fdf35be545d2 100644 (file)
@@ -63,13 +63,13 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) {
             <span className="badge little-spacer-left">
               {translate('hotspot.status', hotspot.resolution || hotspot.status)}
             </span>
-            {hotspot.assignee && hotspot.assignee.name && (
+            {hotspot.assigneeUser && hotspot.assigneeUser.name && (
               <>
                 <span className="huge-spacer-left">{translate('assigned_to')}:</span>
                 <strong className="little-spacer-left">
-                  {hotspot.assignee.active
-                    ? hotspot.assignee.name
-                    : translateWithParameters('user.x_deleted', hotspot.assignee.name)}
+                  {hotspot.assigneeUser.active
+                    ? hotspot.assigneeUser.name
+                    : translateWithParameters('user.x_deleted', hotspot.assigneeUser.name)}
                 </strong>
               </>
             )}
index de1fee9b3c2b21d80fcbac204c60c077dd778106..2dc57fb877044f87034d679e4ea8a192e88db81f 100644 (file)
@@ -17,6 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
+import { sanitize } from 'dompurify';
 import * as React from 'react';
 import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
 import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
@@ -34,9 +36,9 @@ export default function HotspotViewerReviewHistoryTab(props: HotspotViewerReview
   return (
     <>
       {history.map((elt, i) => (
-        <React.Fragment key={`${elt.user.name}-${elt.date}`}>
+        <React.Fragment key={i}>
           {i > 0 && <hr />}
-          <div>
+          <div className="padded">
             <div className="display-flex-center">
               {elt.user.name && (
                 <>
@@ -56,6 +58,11 @@ export default function HotspotViewerReviewHistoryTab(props: HotspotViewerReview
                       {translate('hotspots.tabs.review_history.created')}
                     </span>
                   )}
+                  {elt.type === ReviewHistoryType.Comment && (
+                    <span className="little-spacer-left">
+                      {translate('hotspots.tabs.review_history.comment')}
+                    </span>
+                  )}
                   <span className="little-spacer-left little-spacer-right">-</span>
                 </>
               )}
@@ -64,14 +71,18 @@ export default function HotspotViewerReviewHistoryTab(props: HotspotViewerReview
 
             {elt.type === ReviewHistoryType.Diff && elt.diffs && (
               <div className="spacer-top">
-                {elt.diffs.map(diff => (
-                  <IssueChangelogDiff
-                    diff={diff}
-                    key={`${diff.key}-${diff.oldValue}-${diff.newValue}`}
-                  />
+                {elt.diffs.map((diff, i) => (
+                  <IssueChangelogDiff diff={diff} key={i} />
                 ))}
               </div>
             )}
+
+            {elt.type === ReviewHistoryType.Comment && elt.html && (
+              <div
+                className="spacer-top markdown"
+                dangerouslySetInnerHTML={{ __html: sanitize(elt.html) }}
+              />
+            )}
           </div>
         </React.Fragment>
       ))}
index de17ec8ffad1d0f945fc00875b2f354181489a68..a52facf28bf8eca6488ca7e2419f38551346fed7 100644 (file)
@@ -84,10 +84,10 @@ export default function HotspotViewerTabs(props: HotspotViewerTabsProps) {
         selected={currentTabKey}
         tabs={tabs}
       />
-      <div className="boxed-group big-padded">
+      <div className="bordered">
         {typeof currentTab.content === 'string' ? (
           <div
-            className="markdown"
+            className="markdown big-padded"
             dangerouslySetInnerHTML={{ __html: sanitize(currentTab.content) }}
           />
         ) : (
index 7de5b90f26ef1d2645a4dda0e0ba99d5417df6ff..68820dd230e1bbcb7521caabbdf35d84caebbb05 100644 (file)
@@ -31,12 +31,12 @@ it('should render correctly', () => {
     'unassigned'
   );
   expect(
-    shallowRender({ hotspot: mockHotspot({ assignee: mockUser({ active: false }) }) })
+    shallowRender({ hotspot: mockHotspot({ assigneeUser: mockUser({ active: false }) }) })
   ).toMatchSnapshot('deleted assignee');
   expect(
     shallowRender({
       hotspot: mockHotspot({
-        assignee: mockUser({ name: undefined, login: 'assignee_login' })
+        assigneeUser: mockUser({ name: undefined, login: 'assignee_login' })
       })
     })
   ).toMatchSnapshot('assignee without name');
index e73571cfcc567b74483d140c9772045b8c591439..b724745ab7e2dbb5997956a4e0f7ca35a64a6cb3 100644 (file)
@@ -44,6 +44,10 @@ function shallowRender(props?: Partial<HotspotViewerReviewHistoryTabProps>) {
             { key: 'test', oldValue: 'old', newValue: 'new' },
             { key: 'test-1', oldValue: 'old-1', newValue: 'new-1' }
           ]
+        }),
+        mockHotspotReviewHistoryElement({
+          type: ReviewHistoryType.Comment,
+          html: '<strong>bold text</strong>'
         })
       ]}
       {...props}
index adcc51f65c57c4ea18455330871117151bb6b492..3bd6adbc45ee2d00a8ab92c012f6dbdb66beb3b6 100644 (file)
@@ -13,19 +13,22 @@ exports[`should render correctly 1`] = `
   highlightedSymbols={Array []}
   hotspot={
     Object {
-      "assignee": Object {
+      "assignee": "assignee",
+      "assigneeUser": Object {
         "active": true,
         "local": true,
-        "login": "john.doe",
+        "login": "assignee",
         "name": "John Doe",
       },
-      "author": Object {
+      "author": "author",
+      "authorUser": Object {
         "active": true,
         "local": true,
-        "login": "john.doe",
+        "login": "author",
         "name": "John Doe",
       },
       "changelog": Array [],
+      "comment": Array [],
       "component": Object {
         "breadcrumbs": Array [],
         "key": "my-project",
@@ -90,6 +93,20 @@ exports[`should render correctly 1`] = `
         "startOffset": 26,
       },
       "updateDate": "2013-05-13T17:55:42+0200",
+      "users": Array [
+        Object {
+          "active": true,
+          "local": true,
+          "login": "assignee",
+          "name": "John Doe",
+        },
+        Object {
+          "active": true,
+          "local": true,
+          "login": "author",
+          "name": "John Doe",
+        },
+      ],
     }
   }
   loading={true}
index e4fe72741f27a3d64850ccf838e79e2641105e33..b169213e30e8d48cd669e492fa7f163fd8490326 100644 (file)
@@ -137,19 +137,22 @@ exports[`should render correctly: with sourcelines 1`] = `
         index={0}
         issue={
           Object {
-            "assignee": Object {
+            "assignee": "assignee",
+            "assigneeUser": Object {
               "active": true,
               "local": true,
-              "login": "john.doe",
+              "login": "assignee",
               "name": "John Doe",
             },
-            "author": Object {
+            "author": "author",
+            "authorUser": Object {
               "active": true,
               "local": true,
-              "login": "john.doe",
+              "login": "author",
               "name": "John Doe",
             },
             "changelog": Array [],
+            "comment": Array [],
             "component": Object {
               "breadcrumbs": Array [],
               "key": "my-project",
@@ -214,6 +217,20 @@ exports[`should render correctly: with sourcelines 1`] = `
               "startOffset": 26,
             },
             "updateDate": "2013-05-13T17:55:42+0200",
+            "users": Array [
+              Object {
+                "active": true,
+                "local": true,
+                "login": "assignee",
+                "name": "John Doe",
+              },
+              Object {
+                "active": true,
+                "local": true,
+                "login": "author",
+                "name": "John Doe",
+              },
+            ],
           }
         }
         issuesByLine={Object {}}
index 51794f6d6878a165429905376333ad476c9eee98..6bc661b25074a29793ad1a95d802acbd9425bee5 100644 (file)
@@ -59,19 +59,22 @@ exports[`should render correctly 1`] = `
     <HotspotSnippetContainer
       hotspot={
         Object {
-          "assignee": Object {
+          "assignee": "assignee",
+          "assigneeUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "assignee",
             "name": "John Doe",
           },
-          "author": Object {
+          "author": "author",
+          "authorUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "author",
             "name": "John Doe",
           },
           "changelog": Array [],
+          "comment": Array [],
           "component": Object {
             "breadcrumbs": Array [],
             "key": "my-project",
@@ -136,25 +139,42 @@ exports[`should render correctly 1`] = `
             "startOffset": 26,
           },
           "updateDate": "2013-05-13T17:55:42+0200",
+          "users": Array [
+            Object {
+              "active": true,
+              "local": true,
+              "login": "assignee",
+              "name": "John Doe",
+            },
+            Object {
+              "active": true,
+              "local": true,
+              "login": "author",
+              "name": "John Doe",
+            },
+          ],
         }
       }
     />
     <HotspotViewerTabs
       hotspot={
         Object {
-          "assignee": Object {
+          "assignee": "assignee",
+          "assigneeUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "assignee",
             "name": "John Doe",
           },
-          "author": Object {
+          "author": "author",
+          "authorUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "author",
             "name": "John Doe",
           },
           "changelog": Array [],
+          "comment": Array [],
           "component": Object {
             "breadcrumbs": Array [],
             "key": "my-project",
@@ -219,6 +239,20 @@ exports[`should render correctly 1`] = `
             "startOffset": 26,
           },
           "updateDate": "2013-05-13T17:55:42+0200",
+          "users": Array [
+            Object {
+              "active": true,
+              "local": true,
+              "login": "assignee",
+              "name": "John Doe",
+            },
+            Object {
+              "active": true,
+              "local": true,
+              "login": "author",
+              "name": "John Doe",
+            },
+          ],
         }
       }
     />
@@ -285,19 +319,22 @@ exports[`should render correctly: anonymous user 1`] = `
     <HotspotSnippetContainer
       hotspot={
         Object {
-          "assignee": Object {
+          "assignee": "assignee",
+          "assigneeUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "assignee",
             "name": "John Doe",
           },
-          "author": Object {
+          "author": "author",
+          "authorUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "author",
             "name": "John Doe",
           },
           "changelog": Array [],
+          "comment": Array [],
           "component": Object {
             "breadcrumbs": Array [],
             "key": "my-project",
@@ -362,25 +399,42 @@ exports[`should render correctly: anonymous user 1`] = `
             "startOffset": 26,
           },
           "updateDate": "2013-05-13T17:55:42+0200",
+          "users": Array [
+            Object {
+              "active": true,
+              "local": true,
+              "login": "assignee",
+              "name": "John Doe",
+            },
+            Object {
+              "active": true,
+              "local": true,
+              "login": "author",
+              "name": "John Doe",
+            },
+          ],
         }
       }
     />
     <HotspotViewerTabs
       hotspot={
         Object {
-          "assignee": Object {
+          "assignee": "assignee",
+          "assigneeUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "assignee",
             "name": "John Doe",
           },
-          "author": Object {
+          "author": "author",
+          "authorUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "author",
             "name": "John Doe",
           },
           "changelog": Array [],
+          "comment": Array [],
           "component": Object {
             "breadcrumbs": Array [],
             "key": "my-project",
@@ -445,6 +499,20 @@ exports[`should render correctly: anonymous user 1`] = `
             "startOffset": 26,
           },
           "updateDate": "2013-05-13T17:55:42+0200",
+          "users": Array [
+            Object {
+              "active": true,
+              "local": true,
+              "login": "assignee",
+              "name": "John Doe",
+            },
+            Object {
+              "active": true,
+              "local": true,
+              "login": "author",
+              "name": "John Doe",
+            },
+          ],
         }
       }
     />
@@ -500,19 +568,22 @@ exports[`should render correctly: assignee without name 1`] = `
     <HotspotSnippetContainer
       hotspot={
         Object {
-          "assignee": Object {
+          "assignee": "assignee",
+          "assigneeUser": Object {
             "active": true,
             "local": true,
             "login": "assignee_login",
             "name": undefined,
           },
-          "author": Object {
+          "author": "author",
+          "authorUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "author",
             "name": "John Doe",
           },
           "changelog": Array [],
+          "comment": Array [],
           "component": Object {
             "breadcrumbs": Array [],
             "key": "my-project",
@@ -577,25 +648,42 @@ exports[`should render correctly: assignee without name 1`] = `
             "startOffset": 26,
           },
           "updateDate": "2013-05-13T17:55:42+0200",
+          "users": Array [
+            Object {
+              "active": true,
+              "local": true,
+              "login": "assignee",
+              "name": "John Doe",
+            },
+            Object {
+              "active": true,
+              "local": true,
+              "login": "author",
+              "name": "John Doe",
+            },
+          ],
         }
       }
     />
     <HotspotViewerTabs
       hotspot={
         Object {
-          "assignee": Object {
+          "assignee": "assignee",
+          "assigneeUser": Object {
             "active": true,
             "local": true,
             "login": "assignee_login",
             "name": undefined,
           },
-          "author": Object {
+          "author": "author",
+          "authorUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "author",
             "name": "John Doe",
           },
           "changelog": Array [],
+          "comment": Array [],
           "component": Object {
             "breadcrumbs": Array [],
             "key": "my-project",
@@ -660,6 +748,20 @@ exports[`should render correctly: assignee without name 1`] = `
             "startOffset": 26,
           },
           "updateDate": "2013-05-13T17:55:42+0200",
+          "users": Array [
+            Object {
+              "active": true,
+              "local": true,
+              "login": "assignee",
+              "name": "John Doe",
+            },
+            Object {
+              "active": true,
+              "local": true,
+              "login": "author",
+              "name": "John Doe",
+            },
+          ],
         }
       }
     />
@@ -726,19 +828,22 @@ exports[`should render correctly: deleted assignee 1`] = `
     <HotspotSnippetContainer
       hotspot={
         Object {
-          "assignee": Object {
+          "assignee": "assignee",
+          "assigneeUser": Object {
             "active": false,
             "local": true,
             "login": "john.doe",
             "name": "John Doe",
           },
-          "author": Object {
+          "author": "author",
+          "authorUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "author",
             "name": "John Doe",
           },
           "changelog": Array [],
+          "comment": Array [],
           "component": Object {
             "breadcrumbs": Array [],
             "key": "my-project",
@@ -803,25 +908,42 @@ exports[`should render correctly: deleted assignee 1`] = `
             "startOffset": 26,
           },
           "updateDate": "2013-05-13T17:55:42+0200",
+          "users": Array [
+            Object {
+              "active": true,
+              "local": true,
+              "login": "assignee",
+              "name": "John Doe",
+            },
+            Object {
+              "active": true,
+              "local": true,
+              "login": "author",
+              "name": "John Doe",
+            },
+          ],
         }
       }
     />
     <HotspotViewerTabs
       hotspot={
         Object {
-          "assignee": Object {
+          "assignee": "assignee",
+          "assigneeUser": Object {
             "active": false,
             "local": true,
             "login": "john.doe",
             "name": "John Doe",
           },
-          "author": Object {
+          "author": "author",
+          "authorUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "author",
             "name": "John Doe",
           },
           "changelog": Array [],
+          "comment": Array [],
           "component": Object {
             "breadcrumbs": Array [],
             "key": "my-project",
@@ -886,6 +1008,20 @@ exports[`should render correctly: deleted assignee 1`] = `
             "startOffset": 26,
           },
           "updateDate": "2013-05-13T17:55:42+0200",
+          "users": Array [
+            Object {
+              "active": true,
+              "local": true,
+              "login": "assignee",
+              "name": "John Doe",
+            },
+            Object {
+              "active": true,
+              "local": true,
+              "login": "author",
+              "name": "John Doe",
+            },
+          ],
         }
       }
     />
@@ -944,18 +1080,37 @@ exports[`should render correctly: unassigned 1`] = `
       >
         hotspot.status.FIXED
       </span>
+      <span
+        className="huge-spacer-left"
+      >
+        assigned_to
+        :
+      </span>
+      <strong
+        className="little-spacer-left"
+      >
+        John Doe
+      </strong>
     </div>
     <HotspotSnippetContainer
       hotspot={
         Object {
           "assignee": undefined,
-          "author": Object {
+          "assigneeUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "assignee",
+            "name": "John Doe",
+          },
+          "author": "author",
+          "authorUser": Object {
+            "active": true,
+            "local": true,
+            "login": "author",
             "name": "John Doe",
           },
           "changelog": Array [],
+          "comment": Array [],
           "component": Object {
             "breadcrumbs": Array [],
             "key": "my-project",
@@ -1020,6 +1175,20 @@ exports[`should render correctly: unassigned 1`] = `
             "startOffset": 26,
           },
           "updateDate": "2013-05-13T17:55:42+0200",
+          "users": Array [
+            Object {
+              "active": true,
+              "local": true,
+              "login": "assignee",
+              "name": "John Doe",
+            },
+            Object {
+              "active": true,
+              "local": true,
+              "login": "author",
+              "name": "John Doe",
+            },
+          ],
         }
       }
     />
@@ -1027,13 +1196,21 @@ exports[`should render correctly: unassigned 1`] = `
       hotspot={
         Object {
           "assignee": undefined,
-          "author": Object {
+          "assigneeUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "assignee",
+            "name": "John Doe",
+          },
+          "author": "author",
+          "authorUser": Object {
+            "active": true,
+            "local": true,
+            "login": "author",
             "name": "John Doe",
           },
           "changelog": Array [],
+          "comment": Array [],
           "component": Object {
             "breadcrumbs": Array [],
             "key": "my-project",
@@ -1098,6 +1275,20 @@ exports[`should render correctly: unassigned 1`] = `
             "startOffset": 26,
           },
           "updateDate": "2013-05-13T17:55:42+0200",
+          "users": Array [
+            Object {
+              "active": true,
+              "local": true,
+              "login": "assignee",
+              "name": "John Doe",
+            },
+            Object {
+              "active": true,
+              "local": true,
+              "login": "author",
+              "name": "John Doe",
+            },
+          ],
         }
       }
     />
@@ -1125,19 +1316,22 @@ exports[`should render correctly: user logged in 1`] = `
         <HotspotActions
           hotspot={
             Object {
-              "assignee": Object {
+              "assignee": "assignee",
+              "assigneeUser": Object {
                 "active": true,
                 "local": true,
-                "login": "john.doe",
+                "login": "assignee",
                 "name": "John Doe",
               },
-              "author": Object {
+              "author": "author",
+              "authorUser": Object {
                 "active": true,
                 "local": true,
-                "login": "john.doe",
+                "login": "author",
                 "name": "John Doe",
               },
               "changelog": Array [],
+              "comment": Array [],
               "component": Object {
                 "breadcrumbs": Array [],
                 "key": "my-project",
@@ -1202,6 +1396,20 @@ exports[`should render correctly: user logged in 1`] = `
                 "startOffset": 26,
               },
               "updateDate": "2013-05-13T17:55:42+0200",
+              "users": Array [
+                Object {
+                  "active": true,
+                  "local": true,
+                  "login": "assignee",
+                  "name": "John Doe",
+                },
+                Object {
+                  "active": true,
+                  "local": true,
+                  "login": "author",
+                  "name": "John Doe",
+                },
+              ],
             }
           }
           onSubmit={[MockFunction]}
@@ -1248,19 +1456,22 @@ exports[`should render correctly: user logged in 1`] = `
     <HotspotSnippetContainer
       hotspot={
         Object {
-          "assignee": Object {
+          "assignee": "assignee",
+          "assigneeUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "assignee",
             "name": "John Doe",
           },
-          "author": Object {
+          "author": "author",
+          "authorUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "author",
             "name": "John Doe",
           },
           "changelog": Array [],
+          "comment": Array [],
           "component": Object {
             "breadcrumbs": Array [],
             "key": "my-project",
@@ -1325,25 +1536,42 @@ exports[`should render correctly: user logged in 1`] = `
             "startOffset": 26,
           },
           "updateDate": "2013-05-13T17:55:42+0200",
+          "users": Array [
+            Object {
+              "active": true,
+              "local": true,
+              "login": "assignee",
+              "name": "John Doe",
+            },
+            Object {
+              "active": true,
+              "local": true,
+              "login": "author",
+              "name": "John Doe",
+            },
+          ],
         }
       }
     />
     <HotspotViewerTabs
       hotspot={
         Object {
-          "assignee": Object {
+          "assignee": "assignee",
+          "assigneeUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "assignee",
             "name": "John Doe",
           },
-          "author": Object {
+          "author": "author",
+          "authorUser": Object {
             "active": true,
             "local": true,
-            "login": "john.doe",
+            "login": "author",
             "name": "John Doe",
           },
           "changelog": Array [],
+          "comment": Array [],
           "component": Object {
             "breadcrumbs": Array [],
             "key": "my-project",
@@ -1408,6 +1636,20 @@ exports[`should render correctly: user logged in 1`] = `
             "startOffset": 26,
           },
           "updateDate": "2013-05-13T17:55:42+0200",
+          "users": Array [
+            Object {
+              "active": true,
+              "local": true,
+              "login": "assignee",
+              "name": "John Doe",
+            },
+            Object {
+              "active": true,
+              "local": true,
+              "login": "author",
+              "name": "John Doe",
+            },
+          ],
         }
       }
     />
index c51db022df03543da2b2f3565cb156ed5c379708..a417fe2f20d679c510c74115b8294ccee30a86d6 100644 (file)
@@ -2,7 +2,9 @@
 
 exports[`should render correctly 1`] = `
 <Fragment>
-  <div>
+  <div
+    className="padded"
+  >
     <div
       className="display-flex-center"
     >
@@ -31,7 +33,9 @@ exports[`should render correctly 1`] = `
     </div>
   </div>
   <hr />
-  <div>
+  <div
+    className="padded"
+  >
     <div
       className="display-flex-center"
     >
@@ -59,7 +63,9 @@ exports[`should render correctly 1`] = `
     </div>
   </div>
   <hr />
-  <div>
+  <div
+    className="padded"
+  >
     <div
       className="display-flex-center"
     >
@@ -69,7 +75,9 @@ exports[`should render correctly 1`] = `
     </div>
   </div>
   <hr />
-  <div>
+  <div
+    className="padded"
+  >
     <div
       className="display-flex-center"
     >
@@ -101,7 +109,7 @@ exports[`should render correctly 1`] = `
             "oldValue": "old",
           }
         }
-        key="test-old-new"
+        key="0"
       />
       <IssueChangelogDiff
         diff={
@@ -111,9 +119,47 @@ exports[`should render correctly 1`] = `
             "oldValue": "old-1",
           }
         }
-        key="test-1-old-1-new-1"
+        key="1"
       />
     </div>
   </div>
+  <hr />
+  <div
+    className="padded"
+  >
+    <div
+      className="display-flex-center"
+    >
+      <Connect(Avatar)
+        className="little-spacer-right"
+        name="John Doe"
+        size={20}
+      />
+      <strong>
+        John Doe
+      </strong>
+      <span
+        className="little-spacer-left"
+      >
+        hotspots.tabs.review_history.comment
+      </span>
+      <span
+        className="little-spacer-left little-spacer-right"
+      >
+        -
+      </span>
+      <DateTimeFormatter
+        date="2019-09-13T17:55:42+0200"
+      />
+    </div>
+    <div
+      className="spacer-top markdown"
+      dangerouslySetInnerHTML={
+        Object {
+          "__html": "<strong>bold text</strong>",
+        }
+      }
+    />
+  </div>
 </Fragment>
 `;
index 7262c691e11cbdb8ff8c8b4ad78a2938fb53d55b..9b33f4ab30b984f1a4faddbbae0b175fbf774be8 100644 (file)
@@ -26,7 +26,8 @@ exports[`should render correctly: empty tab 1`] = `
                   "type": 0,
                   "user": Object {
                     "active": true,
-                    "avatar": undefined,
+                    "local": true,
+                    "login": "author",
                     "name": "John Doe",
                   },
                 },
@@ -49,10 +50,10 @@ exports[`should render correctly: empty tab 1`] = `
     }
   />
   <div
-    className="boxed-group big-padded"
+    className="bordered"
   >
     <div
-      className="markdown"
+      className="markdown big-padded"
       dangerouslySetInnerHTML={
         Object {
           "__html": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
@@ -94,7 +95,8 @@ exports[`should render correctly: fix 1`] = `
                   "type": 0,
                   "user": Object {
                     "active": true,
-                    "avatar": undefined,
+                    "local": true,
+                    "login": "author",
                     "name": "John Doe",
                   },
                 },
@@ -117,10 +119,10 @@ exports[`should render correctly: fix 1`] = `
     }
   />
   <div
-    className="boxed-group big-padded"
+    className="bordered"
   >
     <div
-      className="markdown"
+      className="markdown big-padded"
       dangerouslySetInnerHTML={
         Object {
           "__html": "<p>This a <strong>strong</strong> message about fixing !</p>",
@@ -164,7 +166,8 @@ exports[`should render correctly: review 1`] = `
                   "type": 0,
                   "user": Object {
                     "active": true,
-                    "avatar": undefined,
+                    "local": true,
+                    "login": "author",
                     "name": "John Doe",
                   },
                 },
@@ -187,7 +190,7 @@ exports[`should render correctly: review 1`] = `
     }
   />
   <div
-    className="boxed-group big-padded"
+    className="bordered"
   >
     <HotspotViewerReviewHistoryTab
       history={
@@ -197,7 +200,8 @@ exports[`should render correctly: review 1`] = `
             "type": 0,
             "user": Object {
               "active": true,
-              "avatar": undefined,
+              "local": true,
+              "login": "author",
               "name": "John Doe",
             },
           },
@@ -239,7 +243,8 @@ exports[`should render correctly: risk 1`] = `
                   "type": 0,
                   "user": Object {
                     "active": true,
-                    "avatar": undefined,
+                    "local": true,
+                    "login": "author",
                     "name": "John Doe",
                   },
                 },
@@ -262,10 +267,10 @@ exports[`should render correctly: risk 1`] = `
     }
   />
   <div
-    className="boxed-group big-padded"
+    className="bordered"
   >
     <div
-      className="markdown"
+      className="markdown big-padded"
       dangerouslySetInnerHTML={
         Object {
           "__html": "<p>This a <strong>strong</strong> message about risk !</p>",
@@ -307,7 +312,8 @@ exports[`should render correctly: vulnerability 1`] = `
                   "type": 0,
                   "user": Object {
                     "active": true,
-                    "avatar": undefined,
+                    "local": true,
+                    "login": "author",
                     "name": "John Doe",
                   },
                 },
@@ -330,10 +336,10 @@ exports[`should render correctly: vulnerability 1`] = `
     }
   />
   <div
-    className="boxed-group big-padded"
+    className="bordered"
   >
     <div
-      className="markdown"
+      className="markdown big-padded"
       dangerouslySetInnerHTML={
         Object {
           "__html": "<p>This a <strong>strong</strong> message about vulnerability !</p>",
index bed8333ed12d005c4fca13fe10c53e831b69e5c5..7d1de26d4859295ff94149fa11defb75817e90e5 100644 (file)
@@ -89,27 +89,40 @@ export function getHotspotReviewHistory(hotspot: Hotspot): ReviewHistoryElement[
       type: ReviewHistoryType.Creation,
       date: hotspot.creationDate,
       user: {
-        avatar: hotspot.author.avatar,
-        name: hotspot.author.name || hotspot.author.login,
-        active: hotspot.author.active
+        ...hotspot.authorUser,
+        name: hotspot.authorUser.name || hotspot.authorUser.login
       }
     });
   }
 
-  if (hotspot.changelog) {
+  if (hotspot.changelog && hotspot.changelog.length > 0) {
     history.push(
       ...hotspot.changelog.map(log => ({
         type: ReviewHistoryType.Diff,
         date: log.creationDate,
         user: {
+          active: log.isUserActive,
           avatar: log.avatar,
-          name: log.userName || log.user,
-          active: log.isUserActive
+          name: log.userName || log.user
         },
         diffs: log.diffs
       }))
     );
   }
 
+  if (hotspot.comment && hotspot.comment.length > 0) {
+    history.push(
+      ...hotspot.comment.map(comment => ({
+        type: ReviewHistoryType.Comment,
+        date: comment.createdAt,
+        user: {
+          ...comment.user,
+          name: comment.user.name || comment.user.login
+        },
+        html: comment.htmlText
+      }))
+    );
+  }
+
   return sortBy(history, elt => elt.date);
 }
index 4351dc56ecbca45c800f631622e77a41c8365411..a532fdb48a00b0196a2c0655a306bb6b7ea2dc1a 100644 (file)
@@ -50,10 +50,15 @@ export function mockRawHotspot(overrides: Partial<RawHotspot> = {}): RawHotspot
 }
 
 export function mockHotspot(overrides?: Partial<Hotspot>): Hotspot {
+  const assigneeUser = mockUser({ login: 'assignee' });
+  const authorUser = mockUser({ login: 'author' });
   return {
-    assignee: mockUser(),
-    author: mockUser(),
+    assignee: 'assignee',
+    assigneeUser,
+    author: 'author',
+    authorUser,
     changelog: [],
+    comment: [],
     component: mockComponent({ qualifier: ComponentQualifier.File }),
     creationDate: '2013-05-13T17:55:41+0200',
     key: '01fc972e-2a3c-433e-bcae-0bd7f88f5123',
@@ -70,6 +75,7 @@ export function mockHotspot(overrides?: Partial<Hotspot>): Hotspot {
       endOffset: 83
     },
     updateDate: '2013-05-13T17:55:42+0200',
+    users: [assigneeUser, authorUser],
     ...overrides
   };
 }
index a7e3536f4144f6dbff45f77cf9b7a066e53d29c4..29a12a207e3aeeb6b629a0e97715e47247f14a8d 100644 (file)
@@ -69,9 +69,12 @@ export interface RawHotspot {
 }
 
 export interface Hotspot {
-  assignee?: Pick<T.UserBase, 'active' | 'login' | 'name'>;
-  author: Pick<T.UserBase, 'active' | 'avatar' | 'login' | 'name'>;
-  changelog?: T.IssueChangelog[];
+  assignee?: string;
+  assigneeUser?: T.UserBase;
+  author: string;
+  authorUser: T.UserBase;
+  changelog: T.IssueChangelog[];
+  comment: HotspotComment[];
   component: T.Component;
   creationDate: string;
   key: string;
@@ -83,6 +86,7 @@ export interface Hotspot {
   status: string;
   textRange: T.TextRange;
   updateDate: string;
+  users: T.UserBase[];
 }
 
 export interface HotspotUpdateFields {
@@ -104,16 +108,28 @@ export interface HotspotRule {
   vulnerabilityProbability: RiskExposure;
 }
 
+export interface HotspotComment {
+  key: string;
+  htmlText: string;
+  markdown: string;
+  updatable: boolean;
+  createdAt: string;
+  login: string;
+  user: T.UserBase;
+}
+
 export interface ReviewHistoryElement {
   type: ReviewHistoryType;
   date: string;
   user: Pick<T.UserBase, 'active' | 'avatar' | 'name'>;
   diffs?: T.IssueChangelogDiff[];
+  html?: string;
 }
 
 export enum ReviewHistoryType {
   Creation,
-  Diff
+  Diff,
+  Comment
 }
 
 export interface HotspotSearchResponse {
index 8c26488e343ad6fd77fbfe20e2e1fec2954c885a..4a6b30d3984afbca327ad290505ff7107caab4ef 100644 (file)
@@ -661,6 +661,7 @@ hotspots.tabs.vulnerability_description=Are you vulnerable?
 hotspots.tabs.fix_recommendations=How can you fix it?
 hotspots.tabs.review_history=Review history
 hotspots.tabs.review_history.created=created Security Hotspot
+hotspots.tabs.review_history.comment=added a comment
 
 hotspot.change_status.REVIEWED=Change status
 hotspot.change_status.TO_REVIEW=Review Hotspot