From 4eae20b62088816d2d359af0b5d695df2cb95851 Mon Sep 17 00:00:00 2001 From: Mathieu Suen Date: Tue, 6 Apr 2021 16:37:37 +0200 Subject: [PATCH] SONAR-14626 Change source code view permalink to a copy button --- .../SnippetViewer.tsx | 2 + .../__snapshots__/SnippetViewer-test.tsx.snap | 17 ++++++ .../SourceViewer/SourceViewerCode.tsx | 2 + .../SourceViewerCode-test.tsx.snap | 10 ++++ .../SourceViewer/components/Line.css | 10 ++++ .../SourceViewer/components/Line.tsx | 4 +- .../SourceViewer/components/LineNumber.tsx | 24 +++++--- .../components/LineOptionsPopup.tsx | 38 ++++++++----- .../components/__tests__/Line-test.tsx | 1 + .../components/__tests__/LineNumber-test.tsx | 3 +- .../__tests__/LineOptionsPopup-test.tsx | 12 ++-- .../__snapshots__/Line-test.tsx.snap | 6 ++ .../__snapshots__/LineNumber-test.tsx.snap | 55 ++++++++++++++++--- .../LineOptionsPopup-test.tsx.snap | 53 ++++++++++-------- .../js/components/SourceViewer/styles.css | 7 --- server/sonar-web/src/main/js/helpers/urls.ts | 4 +- .../resources/org/sonar/l10n/core.properties | 2 +- 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 { (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 { 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 { 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 { 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 { displaySCM = true, duplications, duplicationsCount, + firstLineNumber, highlighted, highlightedSymbols, issueLocations, @@ -127,7 +129,7 @@ export default class Line extends React.PureComponent { return ( - + {displaySCM && } {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(false); const { line: lineNumber } = line; const hasLineNumber = !!lineNumber; + return hasLineNumber ? ( - } - overlayPlacement={PopupPlacement.RightTop}> + setOpen(false)} + open={isOpen} + overlay={}> + onClick={() => setOpen(true)} + role="button" + tabIndex={0}> {lineNumber} - + ) : ( 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 ( - {({ branchLike, file }) => ( -
- - {translate('component_viewer.get_permalink')} - -
- )} + {({ branchLike, file }) => { + const codeLocation = getCodeUrl(file.project, branchLike, file.key, line.line); + const codeUrl = getPathUrlAsString(codeLocation, false); + const isAtTop = line.line - 4 < firstLineNumber; + return ( + +
    + + {translate('component_viewer.copy_permalink')} + +
+
+ ); + }}
); } 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 = {}) { 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 = {}) { - return shallow(); + return shallow(); } 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().dive(); - expect(wrapper).toMatchSnapshot(); + expect(shallowRender({ line: { line: 10 } }).dive()).toMatchSnapshot(); + expect(shallowRender({ line: { line: 2 } }).dive()).toMatchSnapshot('first line'); }); + +function shallowRender(props: Partial = {}) { + return shallow(); +} 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} > import java.util.ArrayList;", @@ -144,6 +145,7 @@ exports[`should render correctly for last, new, and highlighted lines 1`] = ` data-line-number={16} > import java.util.ArrayList;", @@ -282,6 +284,7 @@ exports[`should render correctly with coverage 1`] = ` data-line-number={16} > import java.util.ArrayList;", @@ -436,6 +439,7 @@ exports[`should render correctly with duplication information 1`] = ` data-line-number={16} > import java.util.ArrayList;", @@ -635,6 +639,7 @@ exports[`should render correctly with issues info 1`] = ` data-line-number={16} > import java.util.ArrayList;", @@ -849,6 +854,7 @@ exports[`should render correctly: no SCM 1`] = ` data-line-number={16} > import java.util.ArrayList;", 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`] = ` - } - overlayPlacement="right-top" > - 3 + 20 - + + +`; + +exports[`should render correctly: first line 1`] = ` + + + } + > + + 12 + + `; 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`] = ` -
- - component_viewer.get_permalink - -
+ + component_viewer.copy_permalink + + + +`; + +exports[`should render correctly: first line 1`] = ` + +
    + + component_viewer.copy_permalink + +
+
`; 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 -- 2.39.5