]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14626 Change source code view permalink to a copy button
authorMathieu Suen <mathieu.suen@sonarsource.com>
Tue, 6 Apr 2021 14:37:37 +0000 (16:37 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 13 Apr 2021 20:03:51 +0000 (20:03 +0000)
17 files changed:
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx
server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerCode-test.tsx.snap
server/sonar-web/src/main/js/components/SourceViewer/components/Line.css
server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineNumber-test.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/Line-test.tsx.snap
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineNumber-test.tsx.snap
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineOptionsPopup-test.tsx.snap
server/sonar-web/src/main/js/components/SourceViewer/styles.css
server/sonar-web/src/main/js/helpers/urls.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 4322c189d3b0180b8f3a75e7e0ec298a5049d43d..4e0cbe6cb8968c7aa2fd458b5da32de52eb59dc8 100644 (file)
@@ -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,
index 1279baf33946065dd1fd83683d96a24f99d5e52b..54632e972dc45fe7b2c4b24fe7cdeb7b97533a1b 100644 (file)
@@ -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 []}
index 25b05fc41c6a183ec30b83e67dd52bf2f0c01d3b..8aa0c34a5d48f814700fb2ecd30ade5e33ebe4d2 100644 (file)
@@ -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,
index cec54e6a9a65129042b1d007519e76826e96ad3c..1784b9b6fa0016bda5fab1ff84a5eeb5ac481389 100644 (file)
@@ -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 []}
index 311e044f4e6f2caa1dc849a5e33e90720dfa2b70..d0e77ccd1f6d076e278afb98ced5ce1a142e40f7 100644 (file)
 .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;
+}
index 40ae17c22c7041c598b8b9ba6333293e785cd7e5..d0b7f207c034d98aa62038c82cba518af253c3b3 100644 (file)
@@ -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 ? (
index 406a5efdde7650be7f687c3e40eb7c71a7932bf7..21df17a4fa2990465f4281add793a800cf26e278 100644 (file)
  * 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" />
index f35a3d6ad1f11fd32de96673db82486c17b8dbb8..24ee901a7ea5ae7943f3ec001b297daf5f124995 100644 (file)
  * 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>
   );
 }
index 710a78ecd1ea518f8e9164d8dd915049e5217552..4ef1b49633a9b94a4c792edd5f5c42234dcc8d84 100644 (file)
@@ -98,6 +98,7 @@ function shallowRender(props: Partial<Line['props']> = {}) {
       displayLocationMarkers={false}
       duplications={[]}
       duplicationsCount={0}
+      firstLineNumber={1}
       highlighted={false}
       highlightedLocationMessage={undefined}
       highlightedSymbols={undefined}
index 6f70d56c2d980d5ca4fbb191984216199989b64c..f536dd12bcb66a5f90310a78e289d4664746b6ac 100644 (file)
@@ -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} />);
 }
index b8b5c1c41b2278d0d51f67b9bbe2b729c41f7a3e..cedd3ed89c48fc60459401bf11140c7e1a1f64b6 100644 (file)
@@ -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} />);
+}
index 2cc3c08322d8644419712a3143c2802bd724dc3b..853ff92974d66eaf984e015d4cbaa80cac2b23c9 100644 (file)
@@ -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>;",
index 0d2a6cc0f783cd8f2ce2abf40bf79bab54780fcf..ea264c2f415a8a4704195a414c90264f3da3e3a9 100644 (file)
@@ -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>
 `;
 
index d0c730eba8936ebd73ccb4801eb128ed5de5fa5c..76fe0f1819ffbfa76510325c374da41fa96dd5f9 100644 (file)
@@ -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>
 `;
index 63393b55f519a05c8ada637725864fc9a0b62cba..ed716732a98e53c1ae46253158e15905b1b685a2 100644 (file)
   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);
index 629480e472b39a47b69fded53a59e00354a7d169..3d2556402691c88ca2a6905873df6f82f643da8e 100644 (file)
@@ -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() }
   };
 }
 
index 8e4490ebcbb76495e209ff51aec030a36c9b4224..d548061fca2193b0637caab5f1fff6401d3912b1 100644 (file)
@@ -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