diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2019-11-04 09:35:56 +0900 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-11-07 11:45:17 +0100 |
commit | f1d6dd1b1b818717baf0f2cf8f3248f0aad3731b (patch) | |
tree | 15a52eeefd79a479f2c81475f315df6e9a865e21 | |
parent | 35f2d76d8dba2fed8cc7476dc2e3c90227667bd7 (diff) | |
download | sonarqube-f1d6dd1b1b818717baf0f2cf8f3248f0aad3731b.tar.gz sonarqube-f1d6dd1b1b818717baf0f2cf8f3248f0aad3731b.zip |
SONAR-12050 show issues' locations marker in CodeViewer
14 files changed, 88 insertions, 2 deletions
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx index 81f3f2621bc..91786a16985 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.tsx @@ -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} diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap index 416ec4a47fd..723c6c1aa3c 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesSourceViewer-test.tsx.snap @@ -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]} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx index da2db7965d4..22b82d5f11a 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx @@ -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} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx index 075a44be474..d6c8104533e 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx @@ -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)} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap index 66538d4b69b..91750341a24 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap @@ -11,6 +11,8 @@ exports[`should render correctly 1`] = ` } component="my-component" displayAllIssues={false} + displayIssueLocationsCount={true} + displayIssueLocationsLink={true} displayLocationMarkers={true} loadComponent={[Function]} loadIssues={[Function]} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx index cc86553eeb5..793c1384180 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx @@ -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} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx index 14ea6fc4f78..d80f2e8706b 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx @@ -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} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.tsx index bd9df8ed497..f64da90def8 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesList.tsx @@ -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} diff --git a/server/sonar-web/src/main/js/components/issue/Issue.css b/server/sonar-web/src/main/js/components/issue/Issue.css index 06861fd5599..fa3824fa2df 100644 --- a/server/sonar-web/src/main/js/components/issue/Issue.css +++ b/server/sonar-web/src/main/js/components/issue/Issue.css @@ -214,7 +214,7 @@ } .issue:not(.selected) .location-index { - background-color: var(--gray80); + background-color: var(--gray60); } .issue .menu:not(.issues-similar-issues-menu):not(.issue-changelog) { diff --git a/server/sonar-web/src/main/js/components/issue/Issue.tsx b/server/sonar-web/src/main/js/components/issue/Issue.tsx index 54721655405..6bfaffd7c17 100644 --- a/server/sonar-web/src/main/js/components/issue/Issue.tsx +++ b/server/sonar-web/src/main/js/components/issue/Issue.tsx @@ -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} diff --git a/server/sonar-web/src/main/js/components/issue/IssueView.tsx b/server/sonar-web/src/main/js/components/issue/IssueView.tsx index d78f5df9e1c..19fd1bea80a 100644 --- a/server/sonar-web/src/main/js/components/issue/IssueView.tsx +++ b/server/sonar-web/src/main/js/components/issue/IssueView.tsx @@ -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} diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx index 2345b65c512..7b52bd5979c 100644 --- a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx @@ -19,10 +19,13 @@ */ 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" diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.tsx index 0f7dcd76eb4..665a17f8a5e 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.tsx +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.tsx @@ -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()} /> diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap index 812027acb3b..3fc0b16bfe3 100644 --- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap @@ -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" |