From cd2200061453646ec5a1563dc68ee5799c18bef3 Mon Sep 17 00:00:00 2001 From: Revanshu Paliwal Date: Thu, 25 May 2023 15:40:11 +0200 Subject: [PATCH] SONAR-19174 Code viewer changes for hotspots page --- .../src/components/IssueLocationMarker.tsx | 74 --------- .../components/__tests__/LineFinding-test.tsx | 51 ++++++ .../__snapshots__/LineFinding-test.tsx.snap | 146 ++++++++++++++++++ .../src/components/code-line/LineFinding.tsx | 12 +- .../src/components/code-line/LineMarker.tsx | 6 +- .../ComponentSourceSnippetGroupViewer.tsx | 34 ++-- .../components/HotspotPrimaryLocationBox.tsx | 25 ++- .../HotspotSnippetContainerRenderer.tsx | 3 +- .../SourceViewer/components/LineCode.tsx | 35 ++--- 9 files changed, 256 insertions(+), 130 deletions(-) delete mode 100644 server/sonar-web/design-system/src/components/IssueLocationMarker.tsx create mode 100644 server/sonar-web/design-system/src/components/__tests__/LineFinding-test.tsx create mode 100644 server/sonar-web/design-system/src/components/__tests__/__snapshots__/LineFinding-test.tsx.snap diff --git a/server/sonar-web/design-system/src/components/IssueLocationMarker.tsx b/server/sonar-web/design-system/src/components/IssueLocationMarker.tsx deleted file mode 100644 index f9886fd80b9..00000000000 --- a/server/sonar-web/design-system/src/components/IssueLocationMarker.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import styled from '@emotion/styled'; -import classNames from 'classnames'; -import { forwardRef, LegacyRef } from 'react'; -import tw from 'twin.macro'; -import { themeColor, themeContrast } from '../helpers/theme'; -import { isDefined } from '../helpers/types'; -import { IssueLocationIcon } from './icons/IssueLocationIcon'; - -interface Props { - className?: string; - onClick?: () => void; - selected: boolean; - text?: number | string; -} - -function IssueLocationMarkerFunc( - { className, onClick, text, selected }: Props, - ref: LegacyRef -) { - return ( - - {isDefined(text) ? text : } - - ); -} - -export const IssueLocationMarker = forwardRef(IssueLocationMarkerFunc); - -export const Marker = styled.span` - ${tw`sw-flex sw-grow-0 sw-items-center sw-justify-center`} - ${tw`sw-body-sm-highlight`} - ${tw`sw-rounded-1/2`} - - height: 1.125rem; - color: ${themeContrast('codeLineLocationMarker')}; - background-color: ${themeColor('codeLineLocationMarker')}; - - &.selected, - &:hover { - background-color: ${themeColor('codeLineLocationMarkerSelected')}; - } - - &:not(.concealed) { - ${tw`sw-px-1`} - ${tw`sw-self-start`} - } -`; diff --git a/server/sonar-web/design-system/src/components/__tests__/LineFinding-test.tsx b/server/sonar-web/design-system/src/components/__tests__/LineFinding-test.tsx new file mode 100644 index 00000000000..30d470d809b --- /dev/null +++ b/server/sonar-web/design-system/src/components/__tests__/LineFinding-test.tsx @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { render } from '../../helpers/testUtils'; +import { FCProps } from '../../types/misc'; +import { LineFinding } from '../code-line/LineFinding'; + +it('should render correctly', async () => { + const user = userEvent.setup(); + const { container } = setupWithProps(); + await user.click(screen.getByRole('button')); + expect(container).toMatchSnapshot(); +}); + +it('should render correctly when issueType is provided', () => { + const { container } = setupWithProps({ issueType: 'bugs' }); + expect(container).toMatchSnapshot(); +}); + +it('should be clickable when onIssueSelect is provided', async () => { + const mockClick = jest.fn(); + const user = userEvent.setup(); + + setupWithProps({ issueType: 'bugs', onIssueSelect: mockClick }); + await user.click(screen.getByRole('button')); + expect(mockClick).toHaveBeenCalled(); +}); + +function setupWithProps(props?: Partial>) { + return render( + + ); +} diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/LineFinding-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/LineFinding-test.tsx.snap new file mode 100644 index 00000000000..b72c648fe60 --- /dev/null +++ b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/LineFinding-test.tsx.snap @@ -0,0 +1,146 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +.emotion-0 { + all: unset; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + gap: 0.5rem; + margin-left: 0.25rem; + margin-right: 0.25rem; + margin-top: 0.75rem; + margin-bottom: 0.75rem; + padding: 0.75rem; + font-family: Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + font-size: 1rem; + line-height: 1.5rem; + font-weight: 600; + border-radius: 0.25rem; + width: 100%; + box-sizing: border-box; + border: 1px solid rgb(253,162,155); + color: rgb(62,67,87); + word-break: break-word; + background-color: rgb(255,255,255); +} + +.emotion-0:focus-visible { + background-color: rgb(239,242,249); +} + +.emotion-0:hover { + box-shadow: 0px 1px 3px 0px rgba(29,33,47,0.05),0px 1px 25px 0px rgba(29,33,47,0.05); +} + +
+ +
+`; + +exports[`should render correctly when issueType is provided 1`] = ` +.emotion-0 { + all: unset; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + gap: 0.5rem; + margin-left: 0.25rem; + margin-right: 0.25rem; + margin-top: 0.75rem; + margin-bottom: 0.75rem; + padding: 0.75rem; + font-family: Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + font-size: 1rem; + line-height: 1.5rem; + font-weight: 600; + border-radius: 0.25rem; + width: 100%; + box-sizing: border-box; + border: 1px solid rgb(253,162,155); + color: rgb(62,67,87); + word-break: break-word; + background-color: rgb(255,255,255); +} + +.emotion-0:focus-visible { + background-color: rgb(239,242,249); +} + +.emotion-0:hover { + box-shadow: 0px 1px 3px 0px rgba(29,33,47,0.05),0px 1px 25px 0px rgba(29,33,47,0.05); +} + +.emotion-2 { + height: 1.5rem; + width: 1.5rem; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + background: rgb(254,205,202); + border-radius: 100%; +} + +
+ +
+`; diff --git a/server/sonar-web/design-system/src/components/code-line/LineFinding.tsx b/server/sonar-web/design-system/src/components/code-line/LineFinding.tsx index 9d2cc794208..052ea396271 100644 --- a/server/sonar-web/design-system/src/components/code-line/LineFinding.tsx +++ b/server/sonar-web/design-system/src/components/code-line/LineFinding.tsx @@ -27,9 +27,9 @@ import { IssueTypeCircleIcon } from '../icons/IssueTypeIcon'; interface Props { className?: string; issueKey: string; - issueType: string; - message: string; - onIssueSelect: (issueKey: string) => void; + issueType?: string; + message: React.ReactNode; + onIssueSelect?: (issueKey: string) => void; selected?: boolean; } @@ -42,12 +42,14 @@ function LineFindingFunc( className={className} data-issue={issueKey} onClick={() => { - onIssueSelect(issueKey); + if (onIssueSelect) { + onIssueSelect(issueKey); + } }} ref={ref} selected={selected} > - + {issueType && } {message} ); diff --git a/server/sonar-web/design-system/src/components/code-line/LineMarker.tsx b/server/sonar-web/design-system/src/components/code-line/LineMarker.tsx index cf62c597baa..092cfc0696c 100644 --- a/server/sonar-web/design-system/src/components/code-line/LineMarker.tsx +++ b/server/sonar-web/design-system/src/components/code-line/LineMarker.tsx @@ -22,7 +22,7 @@ import classNames from 'classnames'; import { forwardRef, Ref, useCallback, useRef } from 'react'; import tw from 'twin.macro'; import { themeColor, themeContrast } from '../../helpers/theme'; -import { IssueLocationMarker } from '../IssueLocationMarker'; +import { LocationMarker } from '../LocationMarker'; interface Props { hideLocationIndex?: boolean; @@ -46,9 +46,9 @@ function LineMarkerFunc( return ( - } selected={selected} text={hideLocationIndex ? undefined : index + 1} /> diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx index fe0935791f5..337e813a485 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx @@ -19,12 +19,13 @@ */ import styled from '@emotion/styled'; import classNames from 'classnames'; -import { FlagMessage, LineFinding, ThemeProp, themeColor, withTheme } from 'design-system'; +import { FlagMessage, LineFinding, themeColor } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { getSources } from '../../../api/components'; import getCoverageStatus from '../../../components/SourceViewer/helpers/getCoverageStatus'; import { locationsByLine } from '../../../components/SourceViewer/helpers/indexing'; +import { IssueMessageHighlighting } from '../../../components/issue/IssueMessageHighlighting'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; import { BranchLike } from '../../../types/branch-like'; @@ -81,10 +82,10 @@ interface State { snippets: Snippet[]; } -class ComponentSourceSnippetGroupViewer extends React.PureComponent { +export default class ComponentSourceSnippetGroupViewer extends React.PureComponent { mounted = false; - constructor(props: Props & ThemeProp) { + constructor(props: Props) { super(props); this.state = { additionalLines: {}, @@ -242,7 +243,12 @@ class ComponentSourceSnippetGroupViewer extends React.PureComponent + } selected={isSelectedIssue} ref={isSelectedIssue ? ctx?.registerPrimaryLocationRef : undefined} onIssueSelect={this.props.onIssueSelect} @@ -257,8 +263,7 @@ class ComponentSourceSnippetGroupViewer extends React.PureComponent + } selected={true} ref={ctx?.registerPrimaryLocationRef} onIssueSelect={this.props.onIssueSelect} @@ -391,4 +395,6 @@ function isExpandable(snippets: Snippet[], snippetGroup: SnippetGroup) { return !fullyShown && isFile(snippetGroup.component.q); } -export default withTheme(ComponentSourceSnippetGroupViewer); +const FileLevelIssueStyle = styled.div` + border: 1px solid ${themeColor('codeLineBorder')}; +`; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotPrimaryLocationBox.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotPrimaryLocationBox.tsx index 6506a0bc9f7..ceba4a4006a 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotPrimaryLocationBox.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotPrimaryLocationBox.tsx @@ -17,11 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import classNames from 'classnames'; +import { LineFinding } from 'design-system'; import * as React from 'react'; import { IssueMessageHighlighting } from '../../../components/issue/IssueMessageHighlighting'; import { Hotspot } from '../../../types/security-hotspots'; -import './HotspotPrimaryLocationBox.css'; const SCROLL_DELAY = 100; const SCROLL_TOP_OFFSET = 100; // 5 lines above @@ -51,23 +50,23 @@ export default function HotspotPrimaryLocationBox(props: HotspotPrimaryLocationB return (
-
- -
+ + } + selected={true} + className="sw-cursor-default" + />
); } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx index 3434874b0d7..48433425bde 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx @@ -62,7 +62,7 @@ export async function animateExpansion( expandBlock: (direction: ExpandDirection) => Promise, direction: ExpandDirection ) { - const wrapper = scrollableRef.current?.querySelector('.snippet'); + const wrapper = scrollableRef.current?.querySelector('.it__source-viewer-code'); const table = wrapper?.firstChild as HTMLElement; if (!wrapper || !table) { @@ -175,6 +175,7 @@ export default function HotspotSnippetContainerRenderer( renderAdditionalChildInLine={renderHotspotBoxInLine} renderDuplicationPopup={noop} snippet={sourceLines} + hideLocationIndex={secondaryLocations.length !== 0} /> )} 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 90c85e342c9..370073cf80b 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,7 +31,7 @@ import { UncoveredUnderlineLabel, UnderlineLabels, } from 'design-system'; -import React, { Fragment, PureComponent, ReactNode, RefObject, createRef } from 'react'; +import React, { PureComponent, ReactNode, RefObject, createRef } from 'react'; import { IssueSourceViewerScrollContext } from '../../../apps/issues/components/IssueSourceViewerScrollContext'; import { translate } from '../../../helpers/l10n'; import { LinearIssueLocation, SourceLine } from '../../../types/types'; @@ -104,21 +104,19 @@ export class LineCode extends PureComponent> { const message = loc?.text; const isLeading = leadingMarker && markerIndex === 0; return ( - - - {(ctx) => ( - - )} - - + + {(ctx) => ( + + )} + ); }; @@ -189,10 +187,7 @@ export class LineCode extends PureComponent> { (previousLine?.coverageStatus && previousLine.coverageBlock === line.coverageBlock); return ( - + {(displayCoverageUnderlineLabel || displayNewCodeUnderlineLabel) && ( {displayCoverageUnderlineLabel && line.coverageStatus === 'covered' && ( -- 2.39.5