]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12050 show issues' locations marker in CodeViewer
authorJeremy Davis <jeremy.davis@sonarsource.com>
Mon, 4 Nov 2019 00:35:56 +0000 (09:35 +0900)
committerSonarTech <sonartech@sonarsource.com>
Thu, 7 Nov 2019 10:45:17 +0000 (11:45 +0100)
14 files changed:
server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx
server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx
server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap
server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.tsx
server/sonar-web/src/main/js/components/issue/Issue.css
server/sonar-web/src/main/js/components/issue/Issue.tsx
server/sonar-web/src/main/js/components/issue/IssueView.tsx
server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.tsx
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap

index 81f3f2621bca094abd4bef6d42480952e9055f4e..91786a16985fdf8b8d2e87499a9db422f253bcbd 100644 (file)
@@ -134,6 +134,8 @@ export default class IssuesSourceViewer extends React.PureComponent<Props> {
             branchLike={this.props.branchLike}
             component={component}
             displayAllIssues={true}
+            displayIssueLocationsCount={true}
+            displayIssueLocationsLink={false}
             displayLocationMarkers={!allMessagesEmpty}
             highlightedLocationMessage={highlightedLocationMessage}
             highlightedLocations={highlightedLocations}
index 416ec4a47fddb0de56ad5c809d4e0c1839498f00..723c6c1aa3cfce08bf3e400b66fd85440902d516 100644 (file)
@@ -279,6 +279,8 @@ exports[`should render SourceViewer correctly 1`] = `
     }
     component="main.js"
     displayAllIssues={true}
+    displayIssueLocationsCount={true}
+    displayIssueLocationsLink={false}
     displayLocationMarkers={false}
     highlightedLocations={Array []}
     loadIssues={[MockFunction]}
index da2db7965d4671c7ef980458ab0cb365573d3cb7..22b82d5f11aa8285c7f2407200e6a157239d4aaf 100644 (file)
@@ -57,6 +57,8 @@ export interface Props {
   branchLike: T.BranchLike | undefined;
   component: string;
   displayAllIssues?: boolean;
+  displayIssueLocationsCount?: boolean;
+  displayIssueLocationsLink?: boolean;
   displayLocationMarkers?: boolean;
   highlightedLine?: number;
   // `undefined` elements mean they are located in a different file,
@@ -123,6 +125,8 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
 
   static defaultProps = {
     displayAllIssues: false,
+    displayIssueLocationsCount: true,
+    displayIssueLocationsLink: true,
     displayLocationMarkers: true,
     loadComponent: defaultLoadComponent,
     loadIssues: defaultLoadIssues,
@@ -612,6 +616,8 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
         branchLike={this.props.branchLike}
         componentKey={this.props.component}
         displayAllIssues={this.props.displayAllIssues}
+        displayIssueLocationsCount={this.props.displayIssueLocationsCount}
+        displayIssueLocationsLink={this.props.displayIssueLocationsLink}
         displayLocationMarkers={this.props.displayLocationMarkers}
         duplications={this.state.duplications}
         duplicationsByLine={this.state.duplicationsByLine}
index 075a44be4740e81fc6f7987b38e2cf7d1be0286a..d6c8104533e51ac3365153b863ef2a2576750864 100644 (file)
@@ -41,6 +41,8 @@ interface Props {
   branchLike: T.BranchLike | undefined;
   componentKey: string;
   displayAllIssues?: boolean;
+  displayIssueLocationsCount?: boolean;
+  displayIssueLocationsLink?: boolean;
   displayLocationMarkers?: boolean;
   duplications: T.Duplication[] | undefined;
   duplicationsByLine: { [line: number]: number[] };
@@ -119,6 +121,8 @@ export default class SourceViewerCode extends React.PureComponent<Props> {
         displayAllIssues={this.props.displayAllIssues}
         displayCoverage={displayCoverage}
         displayDuplications={displayDuplications}
+        displayIssueLocationsCount={this.props.displayIssueLocationsCount}
+        displayIssueLocationsLink={this.props.displayIssueLocationsLink}
         displayIssues={displayIssues}
         displayLocationMarkers={this.props.displayLocationMarkers}
         duplications={this.getDuplicationsForLine(line)}
index 66538d4b69b2b1eee9d6afc52bd50c28e49409ab..91750341a24b63898de5bea4bb506c00028b9ccc 100644 (file)
@@ -11,6 +11,8 @@ exports[`should render correctly 1`] = `
   }
   component="my-component"
   displayAllIssues={false}
+  displayIssueLocationsCount={true}
+  displayIssueLocationsLink={true}
   displayLocationMarkers={true}
   loadComponent={[Function]}
   loadIssues={[Function]}
index cc86553eeb582004ea6f426f7851b5a954fca1c7..793c1384180406011b1f15975a4897689e15903c 100644 (file)
@@ -34,6 +34,8 @@ interface Props {
   displayAllIssues?: boolean;
   displayCoverage: boolean;
   displayDuplications: boolean;
+  displayIssueLocationsCount?: boolean;
+  displayIssueLocationsLink?: boolean;
   displayIssues: boolean;
   displayLocationMarkers?: boolean;
   duplications: number[];
@@ -153,6 +155,8 @@ export default class Line extends React.PureComponent<Props> {
 
         <LineCode
           branchLike={this.props.branchLike}
+          displayIssueLocationsCount={this.props.displayIssueLocationsCount}
+          displayIssueLocationsLink={this.props.displayIssueLocationsLink}
           displayLocationMarkers={this.props.displayLocationMarkers}
           highlightedLocationMessage={this.props.highlightedLocationMessage}
           highlightedSymbols={this.props.highlightedSymbols}
index 14ea6fc4f7811368700787172811c3e4b2ef56e3..d80f2e8706b5678167f1d045bd119e0ba96f4488 100644 (file)
@@ -31,6 +31,8 @@ import LineIssuesList from './LineIssuesList';
 
 interface Props {
   branchLike: T.BranchLike | undefined;
+  displayIssueLocationsCount?: boolean;
+  displayIssueLocationsLink?: boolean;
   displayLocationMarkers?: boolean;
   highlightedLocationMessage: { index: number; text: string | undefined } | undefined;
   highlightedSymbols: string[] | undefined;
@@ -228,6 +230,8 @@ export default class LineCode extends React.PureComponent<Props, State> {
         {showIssues && issues.length > 0 && (
           <LineIssuesList
             branchLike={this.props.branchLike}
+            displayIssueLocationsCount={this.props.displayIssueLocationsCount}
+            displayIssueLocationsLink={this.props.displayIssueLocationsLink}
             issuePopup={this.props.issuePopup}
             issues={issues}
             onIssueChange={this.props.onIssueChange}
index bd9df8ed497526255dfb16983a3b8d32fde0ef50..f64da90def859f515fefcdd953f7a5996ee6c38d 100644 (file)
@@ -22,6 +22,8 @@ import Issue from '../../issue/Issue';
 
 interface Props {
   branchLike: T.BranchLike | undefined;
+  displayIssueLocationsCount?: boolean;
+  displayIssueLocationsLink?: boolean;
   issuePopup: { issue: string; name: string } | undefined;
   issues: T.Issue[];
   onIssueChange: (issue: T.Issue) => void;
@@ -38,6 +40,8 @@ export default function LineIssuesList(props: Props) {
       {props.issues.map(issue => (
         <Issue
           branchLike={props.branchLike}
+          displayLocationsCount={props.displayIssueLocationsCount}
+          displayLocationsLink={props.displayIssueLocationsLink}
           issue={issue}
           key={issue.key}
           onChange={props.onIssueChange}
index 06861fd559961c4e1ebb999621cd85b8f418162b..fa3824fa2df98cff0fb7c81339a354f5caa656d2 100644 (file)
 }
 
 .issue:not(.selected) .location-index {
-  background-color: var(--gray80);
+  background-color: var(--gray60);
 }
 
 .issue .menu:not(.issues-similar-issues-menu):not(.issue-changelog) {
index 547216554051321ad20adf8be39677de4739b92f..6bfaffd7c17329e53a38e3cf1a190fc42e8b584e 100644 (file)
@@ -27,6 +27,8 @@ import IssueView from './IssueView';
 interface Props {
   branchLike?: T.BranchLike;
   checked?: boolean;
+  displayLocationsCount?: boolean;
+  displayLocationsLink?: boolean;
   issue: T.Issue;
   onChange: (issue: T.Issue) => void;
   onCheck?: (issue: string) => void;
@@ -130,6 +132,8 @@ export default class Issue extends React.PureComponent<Props> {
         branchLike={this.props.branchLike}
         checked={this.props.checked}
         currentPopup={this.props.openPopup}
+        displayLocationsCount={this.props.displayLocationsCount}
+        displayLocationsLink={this.props.displayLocationsLink}
         issue={this.props.issue}
         onAssign={this.handleAssignement}
         onChange={this.props.onChange}
index d78f5df9e1cd40531a5ce01f1937a76f53c5a84c..19fd1bea80a380ff5906108d15be1da9ffe106be 100644 (file)
@@ -30,6 +30,8 @@ interface Props {
   branchLike?: T.BranchLike;
   checked?: boolean;
   currentPopup?: string;
+  displayLocationsCount?: boolean;
+  displayLocationsLink?: boolean;
   issue: T.Issue;
   onAssign: (login: string) => void;
   onChange: (issue: T.Issue) => void;
@@ -83,6 +85,8 @@ export default class IssueView extends React.PureComponent<Props> {
         <IssueTitleBar
           branchLike={this.props.branchLike}
           currentPopup={this.props.currentPopup}
+          displayLocationsCount={this.props.displayLocationsCount}
+          displayLocationsLink={this.props.displayLocationsLink}
           issue={issue}
           onFilter={this.props.onFilter}
           togglePopup={this.props.togglePopup}
index 2345b65c512cba25799b2e26201e9e4e7b58bbe4..7b52bd5979c990dad4a726c498077c8a6e6bea40 100644 (file)
  */
 import * as React from 'react';
 import { Link } from 'react-router';
+import Tooltip from 'sonar-ui-common/components/controls/Tooltip';
 import LinkIcon from 'sonar-ui-common/components/icons/LinkIcon';
-import { translate } from 'sonar-ui-common/helpers/l10n';
+import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
+import { formatMeasure } from 'sonar-ui-common/helpers/measures';
 import { getBranchLikeQuery } from '../../../helpers/branches';
 import { getComponentIssuesUrl } from '../../../helpers/urls';
+import LocationIndex from '../../common/LocationIndex';
 import { WorkspaceContext } from '../../workspace/context';
 import IssueChangelog from './IssueChangelog';
 import IssueMessage from './IssueMessage';
@@ -31,6 +34,8 @@ import SimilarIssuesFilter from './SimilarIssuesFilter';
 interface Props {
   branchLike?: T.BranchLike;
   currentPopup?: string;
+  displayLocationsCount?: boolean;
+  displayLocationsLink?: boolean;
   issue: T.Issue;
   onFilter?: (property: string, issue: T.Issue) => void;
   togglePopup: (popup: string, show?: boolean) => void;
@@ -40,6 +45,22 @@ export default function IssueTitleBar(props: Props) {
   const { issue } = props;
   const hasSimilarIssuesFilter = props.onFilter != null;
 
+  const locationsCount =
+    issue.secondaryLocations.length +
+    issue.flows.reduce((sum, locations) => sum + locations.length, 0);
+
+  const locationsBadge = (
+    <Tooltip
+      overlay={translateWithParameters(
+        'issue.this_issue_involves_x_code_locations',
+        formatMeasure(locationsCount, 'INT')
+      )}>
+      <LocationIndex>{locationsCount}</LocationIndex>
+    </Tooltip>
+  );
+
+  const displayLocations = props.displayLocationsCount && locationsCount > 0;
+
   const issueUrl = getComponentIssuesUrl(issue.project, {
     ...getBranchLikeQuery(props.branchLike),
     issues: issue.key,
@@ -79,6 +100,17 @@ export default function IssueTitleBar(props: Props) {
               </span>
             </li>
           )}
+          {displayLocations && (
+            <li className="issue-meta">
+              {props.displayLocationsLink ? (
+                <Link target="_blank" to={issueUrl}>
+                  {locationsBadge}
+                </Link>
+              ) : (
+                locationsBadge
+              )}
+            </li>
+          )}
           <li className="issue-meta">
             <Link
               className="js-issue-permalink link-no-underline"
index 0f7dcd76eb4b3abad5848aea7c575a7eb966b9fe..665a17f8a5e13346ae1a88058b5e54cdbe30802e 100644 (file)
@@ -23,6 +23,7 @@ import { mockIssue } from '../../../../helpers/testMocks';
 import IssueTitleBar from '../IssueTitleBar';
 
 const issue: T.Issue = mockIssue();
+const issueWithLocations: T.Issue = mockIssue(true);
 
 it('should render the titlebar correctly', () => {
   const branch: T.ShortLivingBranch = {
@@ -44,6 +45,17 @@ it('should render the titlebar with the filter', () => {
   expect(element).toMatchSnapshot();
 });
 
+it('should count all code locations', () => {
+  const element = shallow(
+    <IssueTitleBar
+      displayLocationsCount={true}
+      issue={issueWithLocations}
+      togglePopup={jest.fn()}
+    />
+  );
+  expect(element.find('LocationIndex')).toMatchSnapshot();
+});
+
 it('should have a correct permalink for security hotspots', () => {
   const wrapper = shallow(
     <IssueTitleBar issue={{ ...issue, type: 'SECURITY_HOTSPOT' }} togglePopup={jest.fn()} />
index 812027acb3bfbf07cfa4a3334fb97f3aa27131da..3fc0b16bfe37e4e63287b741e5ee4745e83d7258 100644 (file)
@@ -1,5 +1,11 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`should count all code locations 1`] = `
+<LocationIndex>
+  7
+</LocationIndex>
+`;
+
 exports[`should render the titlebar correctly 1`] = `
 <div
   className="issue-row"