branchLike={this.props.branchLike}
component={component}
displayAllIssues={true}
+ displayIssueLocationsCount={true}
+ displayIssueLocationsLink={false}
displayLocationMarkers={!allMessagesEmpty}
highlightedLocationMessage={highlightedLocationMessage}
highlightedLocations={highlightedLocations}
}
component="main.js"
displayAllIssues={true}
+ displayIssueLocationsCount={true}
+ displayIssueLocationsLink={false}
displayLocationMarkers={false}
highlightedLocations={Array []}
loadIssues={[MockFunction]}
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,
static defaultProps = {
displayAllIssues: false,
+ displayIssueLocationsCount: true,
+ displayIssueLocationsLink: true,
displayLocationMarkers: true,
loadComponent: defaultLoadComponent,
loadIssues: defaultLoadIssues,
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}
branchLike: T.BranchLike | undefined;
componentKey: string;
displayAllIssues?: boolean;
+ displayIssueLocationsCount?: boolean;
+ displayIssueLocationsLink?: boolean;
displayLocationMarkers?: boolean;
duplications: T.Duplication[] | undefined;
duplicationsByLine: { [line: number]: number[] };
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)}
}
component="my-component"
displayAllIssues={false}
+ displayIssueLocationsCount={true}
+ displayIssueLocationsLink={true}
displayLocationMarkers={true}
loadComponent={[Function]}
loadIssues={[Function]}
displayAllIssues?: boolean;
displayCoverage: boolean;
displayDuplications: boolean;
+ displayIssueLocationsCount?: boolean;
+ displayIssueLocationsLink?: boolean;
displayIssues: boolean;
displayLocationMarkers?: boolean;
duplications: number[];
<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}
interface Props {
branchLike: T.BranchLike | undefined;
+ displayIssueLocationsCount?: boolean;
+ displayIssueLocationsLink?: boolean;
displayLocationMarkers?: boolean;
highlightedLocationMessage: { index: number; text: string | undefined } | undefined;
highlightedSymbols: string[] | undefined;
{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}
interface Props {
branchLike: T.BranchLike | undefined;
+ displayIssueLocationsCount?: boolean;
+ displayIssueLocationsLink?: boolean;
issuePopup: { issue: string; name: string } | undefined;
issues: T.Issue[];
onIssueChange: (issue: T.Issue) => void;
{props.issues.map(issue => (
<Issue
branchLike={props.branchLike}
+ displayLocationsCount={props.displayIssueLocationsCount}
+ displayLocationsLink={props.displayIssueLocationsLink}
issue={issue}
key={issue.key}
onChange={props.onIssueChange}
}
.issue:not(.selected) .location-index {
- background-color: var(--gray80);
+ background-color: var(--gray60);
}
.issue .menu:not(.issues-similar-issues-menu):not(.issue-changelog) {
interface Props {
branchLike?: T.BranchLike;
checked?: boolean;
+ displayLocationsCount?: boolean;
+ displayLocationsLink?: boolean;
issue: T.Issue;
onChange: (issue: T.Issue) => void;
onCheck?: (issue: string) => void;
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}
branchLike?: T.BranchLike;
checked?: boolean;
currentPopup?: string;
+ displayLocationsCount?: boolean;
+ displayLocationsLink?: boolean;
issue: T.Issue;
onAssign: (login: string) => void;
onChange: (issue: T.Issue) => void;
<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}
*/
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';
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;
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,
</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"
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 = {
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()} />
// 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"