aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap17
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerCode-test.tsx.snap10
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/Line.css10
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx24
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx38
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineNumber-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx12
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/Line-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineNumber-test.tsx.snap55
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineOptionsPopup-test.tsx.snap53
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/styles.css7
-rw-r--r--server/sonar-web/src/main/js/helpers/urls.ts4
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties2
17 files changed, 183 insertions, 67 deletions
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx
index 4322c189d3b..4e0cbe6cb89 100644
--- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx
@@ -136,6 +136,7 @@ export default class SnippetViewer extends React.PureComponent<Props> {
(duplicationsCount && duplicationsByLine && duplicationsByLine[line.line]) || [];
const isSinkLine = issuesForLine.some(i => i.key === this.props.issue.key);
+ const firstLineNumber = snippet && snippet.length ? snippet[0].line : 0;
const noop = () => {};
return (
@@ -149,6 +150,7 @@ export default class SnippetViewer extends React.PureComponent<Props> {
displaySCM={displaySCM}
duplications={lineDuplications}
duplicationsCount={duplicationsCount}
+ firstLineNumber={firstLineNumber}
highlighted={false}
highlightedLocationMessage={optimizeLocationMessage(
this.props.highlightedLocationMessage,
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap
index 1279baf3394..54632e972dc 100644
--- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap
@@ -36,6 +36,7 @@ exports[`should render correctly 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={5}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -84,6 +85,7 @@ exports[`should render correctly 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={5}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -145,6 +147,7 @@ exports[`should render correctly 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={5}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -243,6 +246,7 @@ exports[`should render correctly when at the bottom of the file 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={10}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -291,6 +295,7 @@ exports[`should render correctly when at the bottom of the file 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={10}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -352,6 +357,7 @@ exports[`should render correctly when at the bottom of the file 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={10}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -413,6 +419,7 @@ exports[`should render correctly when at the bottom of the file 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={10}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -500,6 +507,7 @@ exports[`should render correctly when at the top of the file 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={1}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -548,6 +556,7 @@ exports[`should render correctly when at the top of the file 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={1}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -609,6 +618,7 @@ exports[`should render correctly when at the top of the file 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={1}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -670,6 +680,7 @@ exports[`should render correctly when at the top of the file 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={1}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -731,6 +742,7 @@ exports[`should render correctly when at the top of the file 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={1}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -792,6 +804,7 @@ exports[`should render correctly when at the top of the file 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={1}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -853,6 +866,7 @@ exports[`should render correctly when at the top of the file 1`] = `
displayLocationMarkers={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={1}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -952,6 +966,7 @@ exports[`should render correctly with no SCM 1`] = `
displaySCM={false}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={5}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -1001,6 +1016,7 @@ exports[`should render correctly with no SCM 1`] = `
displaySCM={false}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={5}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -1063,6 +1079,7 @@ exports[`should render correctly with no SCM 1`] = `
displaySCM={false}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={5}
highlighted={false}
issueLocations={Array []}
issues={Array []}
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 25b05fc41c6..8aa0c34a5d4 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx
@@ -129,6 +129,7 @@ export default class SourceViewerCode extends React.PureComponent<Props> {
const duplicationsCount = this.props.duplications ? this.props.duplications.length : 0;
const issuesForLine = this.getIssuesForLine(line);
+ const firstLineNumber = sources && sources.length ? sources[0].line : 0;
let scrollToUncoveredLine = false;
if (
@@ -155,6 +156,7 @@ export default class SourceViewerCode extends React.PureComponent<Props> {
displayLocationMarkers={this.props.displayLocationMarkers}
duplications={this.getDuplicationsForLine(line)}
duplicationsCount={duplicationsCount}
+ firstLineNumber={firstLineNumber}
highlighted={line.line === this.props.highlightedLine}
highlightedLocationMessage={optimizeLocationMessage(
highlightedLocationMessage,
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerCode-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerCode-test.tsx.snap
index cec54e6a9a6..1784b9b6fa0 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerCode-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerCode-test.tsx.snap
@@ -22,6 +22,7 @@ exports[`should render correctly: default 1`] = `
displayIssues={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={16}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -68,6 +69,7 @@ exports[`should render correctly: default 1`] = `
displayIssues={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={16}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -127,6 +129,7 @@ exports[`should render correctly: default 1`] = `
displayIssues={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={16}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -199,6 +202,7 @@ exports[`should render correctly: has file level issues 1`] = `
displayIssues={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={16}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -240,6 +244,7 @@ exports[`should render correctly: has file level issues 1`] = `
displayIssues={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={16}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -286,6 +291,7 @@ exports[`should render correctly: has file level issues 1`] = `
displayIssues={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={16}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -345,6 +351,7 @@ exports[`should render correctly: has file level issues 1`] = `
displayIssues={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={16}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -427,6 +434,7 @@ exports[`should render correctly: has more sources 1`] = `
displayIssues={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={16}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -473,6 +481,7 @@ exports[`should render correctly: has more sources 1`] = `
displayIssues={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={16}
highlighted={false}
issueLocations={Array []}
issues={Array []}
@@ -532,6 +541,7 @@ exports[`should render correctly: has more sources 1`] = `
displayIssues={true}
duplications={Array []}
duplicationsCount={0}
+ firstLineNumber={16}
highlighted={false}
issueLocations={Array []}
issues={Array []}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.css b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.css
index 311e044f4e6..d0e77ccd1f6 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.css
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.css
@@ -241,3 +241,13 @@
.source-line-duplicated {
background-color: #797979 !important;
}
+
+.source-viewer-bubble-popup a {
+ font-family: var(--baseFontFamily);
+ font-size: var(--baseFontSize);
+ text-align: left;
+ user-select: text;
+ border-bottom: none;
+ transition: none;
+ color: unset;
+}
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 40ae17c22c7..d0b7f207c03 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
@@ -41,6 +41,7 @@ interface Props {
displaySCM?: boolean;
duplications: number[];
duplicationsCount: number;
+ firstLineNumber: number;
highlighted: boolean;
highlightedLocationMessage: { index: number; text: string | undefined } | undefined;
highlightedSymbols: string[] | undefined;
@@ -99,6 +100,7 @@ export default class Line extends React.PureComponent<Props> {
displaySCM = true,
duplications,
duplicationsCount,
+ firstLineNumber,
highlighted,
highlightedSymbols,
issueLocations,
@@ -127,7 +129,7 @@ export default class Line extends React.PureComponent<Props> {
return (
<tr className={className} data-line-number={line.line}>
- <LineNumber line={line} />
+ <LineNumber firstLineNumber={firstLineNumber} line={line} />
{displaySCM && <LineSCM line={line} previousLine={previousLine} />}
{displayIssues && !displayAllIssues ? (
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx
index 406a5efdde7..21df17a4fa2 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx
@@ -18,29 +18,37 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import Dropdown from 'sonar-ui-common/components/controls/Dropdown';
-import { PopupPlacement } from 'sonar-ui-common/components/ui/popups';
+import Toggler from 'sonar-ui-common/components/controls/Toggler';
import { translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import LineOptionsPopup from './LineOptionsPopup';
export interface LineNumberProps {
+ firstLineNumber: number;
line: T.SourceLine;
}
-export function LineNumber({ line }: LineNumberProps) {
+export function LineNumber({ firstLineNumber, line }: LineNumberProps) {
+ const [isOpen, setOpen] = React.useState<boolean>(false);
const { line: lineNumber } = line;
const hasLineNumber = !!lineNumber;
+
return hasLineNumber ? (
<td className="source-meta source-line-number" data-line-number={lineNumber}>
- <Dropdown
- overlay={<LineOptionsPopup line={line} />}
- overlayPlacement={PopupPlacement.RightTop}>
+ <Toggler
+ closeOnClickOutside={true}
+ onRequestClose={() => setOpen(false)}
+ open={isOpen}
+ overlay={<LineOptionsPopup firstLineNumber={firstLineNumber} line={line} />}>
<span
+ aria-expanded={isOpen}
+ aria-haspopup={true}
aria-label={translateWithParameters('source_viewer.line_X', lineNumber)}
- role="button">
+ onClick={() => setOpen(true)}
+ role="button"
+ tabIndex={0}>
{lineNumber}
</span>
- </Dropdown>
+ </Toggler>
</td>
) : (
<td className="source-meta source-line-number" />
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx
index f35a3d6ad1f..24ee901a7ea 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx
@@ -18,29 +18,39 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Link } from 'react-router';
+import { ActionsDropdownItem } from 'sonar-ui-common/components/controls/ActionsDropdown';
+import { DropdownOverlay } from 'sonar-ui-common/components/controls/Dropdown';
+import { PopupPlacement } from 'sonar-ui-common/components/ui/popups';
import { translate } from 'sonar-ui-common/helpers/l10n';
+import { getPathUrlAsString } from 'sonar-ui-common/helpers/urls';
import { getCodeUrl } from '../../../helpers/urls';
import { SourceViewerContext } from '../SourceViewerContext';
-interface LineOptionsPopupProps {
+export interface LineOptionsPopupProps {
+ firstLineNumber: number;
line: T.SourceLine;
}
-export function LineOptionsPopup({ line }: LineOptionsPopupProps) {
+export function LineOptionsPopup({ firstLineNumber, line }: LineOptionsPopupProps) {
return (
<SourceViewerContext.Consumer>
- {({ branchLike, file }) => (
- <div className="source-viewer-bubble-popup nowrap">
- <Link
- className="js-get-permalink"
- rel="noopener noreferrer"
- target="_blank"
- to={getCodeUrl(file.project, branchLike, file.key, line.line)}>
- {translate('component_viewer.get_permalink')}
- </Link>
- </div>
- )}
+ {({ branchLike, file }) => {
+ const codeLocation = getCodeUrl(file.project, branchLike, file.key, line.line);
+ const codeUrl = getPathUrlAsString(codeLocation, false);
+ const isAtTop = line.line - 4 < firstLineNumber;
+ return (
+ <DropdownOverlay
+ className="big-spacer-left"
+ noPadding={true}
+ placement={isAtTop ? PopupPlacement.BottomLeft : PopupPlacement.TopLeft}>
+ <ul className="padded source-viewer-bubble-popup nowrap">
+ <ActionsDropdownItem copyValue={codeUrl}>
+ {translate('component_viewer.copy_permalink')}
+ </ActionsDropdownItem>
+ </ul>
+ </DropdownOverlay>
+ );
+ }}
</SourceViewerContext.Consumer>
);
}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx
index 710a78ecd1e..4ef1b49633a 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx
@@ -98,6 +98,7 @@ function shallowRender(props: Partial<Line['props']> = {}) {
displayLocationMarkers={false}
duplications={[]}
duplicationsCount={0}
+ firstLineNumber={1}
highlighted={false}
highlightedLocationMessage={undefined}
highlightedSymbols={undefined}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineNumber-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineNumber-test.tsx
index 6f70d56c2d9..f536dd12bcb 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineNumber-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineNumber-test.tsx
@@ -24,8 +24,9 @@ import { LineNumber, LineNumberProps } from '../LineNumber';
it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ line: { line: 0 } })).toMatchSnapshot('no line number');
+ expect(shallowRender({ line: { line: 12 } })).toMatchSnapshot('first line');
});
function shallowRender(props: Partial<LineNumberProps> = {}) {
- return shallow(<LineNumber line={{ line: 3 }} {...props} />);
+ return shallow(<LineNumber firstLineNumber={10} line={{ line: 20 }} {...props} />);
}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx
index b8b5c1c41b2..cedd3ed89c4 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx
@@ -20,7 +20,8 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockBranch } from '../../../../helpers/mocks/branch-like';
-import { LineOptionsPopup } from '../LineOptionsPopup';
+import { mockSourceLine } from '../../../../helpers/testMocks';
+import { LineOptionsPopup, LineOptionsPopupProps } from '../LineOptionsPopup';
jest.mock('../../SourceViewerContext', () => ({
SourceViewerContext: {
@@ -33,7 +34,10 @@ jest.mock('../../SourceViewerContext', () => ({
}));
it('should render correctly', () => {
- const line = { line: 3 };
- const wrapper = shallow(<LineOptionsPopup line={line} />).dive();
- expect(wrapper).toMatchSnapshot();
+ expect(shallowRender({ line: { line: 10 } }).dive()).toMatchSnapshot();
+ expect(shallowRender({ line: { line: 2 } }).dive()).toMatchSnapshot('first line');
});
+
+function shallowRender(props: Partial<LineOptionsPopupProps> = {}) {
+ return shallow(<LineOptionsPopup firstLineNumber={1} line={mockSourceLine()} {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/Line-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/Line-test.tsx.snap
index 2cc3c08322d..853ff92974d 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/Line-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/Line-test.tsx.snap
@@ -6,6 +6,7 @@ exports[`should render correctly 1`] = `
data-line-number={16}
>
<Memo(LineNumber)
+ firstLineNumber={1}
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -144,6 +145,7 @@ exports[`should render correctly for last, new, and highlighted lines 1`] = `
data-line-number={16}
>
<Memo(LineNumber)
+ firstLineNumber={1}
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -282,6 +284,7 @@ exports[`should render correctly with coverage 1`] = `
data-line-number={16}
>
<Memo(LineNumber)
+ firstLineNumber={1}
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -436,6 +439,7 @@ exports[`should render correctly with duplication information 1`] = `
data-line-number={16}
>
<Memo(LineNumber)
+ firstLineNumber={1}
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -635,6 +639,7 @@ exports[`should render correctly with issues info 1`] = `
data-line-number={16}
>
<Memo(LineNumber)
+ firstLineNumber={1}
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -849,6 +854,7 @@ exports[`should render correctly: no SCM 1`] = `
data-line-number={16}
>
<Memo(LineNumber)
+ firstLineNumber={1}
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineNumber-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineNumber-test.tsx.snap
index 0d2a6cc0f78..ea264c2f415 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineNumber-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineNumber-test.tsx.snap
@@ -3,27 +3,68 @@
exports[`should render correctly: default 1`] = `
<td
className="source-meta source-line-number"
- data-line-number={3}
+ data-line-number={20}
>
- <Dropdown
+ <Toggler
+ closeOnClickOutside={true}
+ onRequestClose={[Function]}
+ open={false}
overlay={
<Memo(LineOptionsPopup)
+ firstLineNumber={10}
line={
Object {
- "line": 3,
+ "line": 20,
}
}
/>
}
- overlayPlacement="right-top"
>
<span
- aria-label="source_viewer.line_X.3"
+ aria-expanded={false}
+ aria-haspopup={true}
+ aria-label="source_viewer.line_X.20"
+ onClick={[Function]}
role="button"
+ tabIndex={0}
>
- 3
+ 20
</span>
- </Dropdown>
+ </Toggler>
+</td>
+`;
+
+exports[`should render correctly: first line 1`] = `
+<td
+ className="source-meta source-line-number"
+ data-line-number={12}
+>
+ <Toggler
+ closeOnClickOutside={true}
+ onRequestClose={[Function]}
+ open={false}
+ overlay={
+ <Memo(LineOptionsPopup)
+ firstLineNumber={10}
+ line={
+ Object {
+ "line": 12,
+ }
+ }
+ />
+ }
+ >
+ <span
+ aria-expanded={false}
+ aria-haspopup={true}
+ aria-label="source_viewer.line_X.12"
+ onClick={[Function]}
+ role="button"
+ tabIndex={0}
+ >
+ 12
+ </span>
+ </Toggler>
</td>
`;
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineOptionsPopup-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineOptionsPopup-test.tsx.snap
index d0c730eba89..76fe0f1819f 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineOptionsPopup-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineOptionsPopup-test.tsx.snap
@@ -1,28 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render correctly 1`] = `
-<div
- className="source-viewer-bubble-popup nowrap"
+<DropdownOverlay
+ className="big-spacer-left"
+ noPadding={true}
+ placement="top-left"
>
- <Link
- className="js-get-permalink"
- onlyActiveOnIndex={false}
- rel="noopener noreferrer"
- style={Object {}}
- target="_blank"
- to={
- Object {
- "pathname": "/code",
- "query": Object {
- "branch": "feature",
- "id": "prj",
- "line": 3,
- "selected": "foo",
- },
- }
- }
+ <ul
+ className="padded source-viewer-bubble-popup nowrap"
>
- component_viewer.get_permalink
- </Link>
-</div>
+ <ActionsDropdownItem
+ copyValue="http://localhost/code?id=prj&branch=feature&selected=foo&line=10"
+ >
+ component_viewer.copy_permalink
+ </ActionsDropdownItem>
+ </ul>
+</DropdownOverlay>
+`;
+
+exports[`should render correctly: first line 1`] = `
+<DropdownOverlay
+ className="big-spacer-left"
+ noPadding={true}
+ placement="bottom-left"
+>
+ <ul
+ className="padded source-viewer-bubble-popup nowrap"
+ >
+ <ActionsDropdownItem
+ copyValue="http://localhost/code?id=prj&branch=feature&selected=foo&line=2"
+ >
+ component_viewer.copy_permalink
+ </ActionsDropdownItem>
+ </ul>
+</DropdownOverlay>
`;
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/styles.css b/server/sonar-web/src/main/js/components/SourceViewer/styles.css
index 63393b55f51..ed716732a98 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/styles.css
+++ b/server/sonar-web/src/main/js/components/SourceViewer/styles.css
@@ -115,13 +115,6 @@
border-top: 1px solid var(--barBorderColor);
}
-.source-viewer-bubble-popup {
- font-family: var(--baseFontFamily);
- font-size: var(--baseFontSize);
- text-align: left;
- user-select: text;
-}
-
.issue-location.highlighted {
border-color: var(--issueLocationHighlighted);
background-color: var(--issueLocationHighlighted);
diff --git a/server/sonar-web/src/main/js/helpers/urls.ts b/server/sonar-web/src/main/js/helpers/urls.ts
index 629480e472b..3d255640269 100644
--- a/server/sonar-web/src/main/js/helpers/urls.ts
+++ b/server/sonar-web/src/main/js/helpers/urls.ts
@@ -249,10 +249,10 @@ export function getCodeUrl(
branchLike?: BranchLike,
selected?: string,
line?: number
-) {
+): Location {
return {
pathname: '/code',
- query: { id: project, ...getBranchLikeQuery(branchLike), selected, line }
+ query: { id: project, ...getBranchLikeQuery(branchLike), selected, line: line?.toFixed() }
};
}
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 8e4490ebcbb..d548061fca2 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -2635,7 +2635,7 @@ component_viewer.show_raw_source=Show Raw Source
component_viewer.more_actions=More Actions
component_viewer.new_window=Open in New Window
component_viewer.open_in_workspace=Pin This File
-component_viewer.get_permalink=Get Permalink
+component_viewer.copy_permalink=Copy Permalink
component_viewer.covered_lines=Covered Lines
component_viewer.show_details=Show Measures
component_viewer.file_measures=File measures