From: Wouter Admiraal Date: Mon, 8 Apr 2019 07:58:28 +0000 (+0200) Subject: SONAR-8741 Display breakdown by issue types in SourceViewerHeader X-Git-Tag: 7.8~361 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=f6df5fd53b68e63731fdec370778f827cddd6a7b;p=sonarqube.git SONAR-8741 Display breakdown by issue types in SourceViewerHeader * Only display the measure breakdown on Code --- diff --git a/server/sonar-web/src/main/js/apps/code/components/SourceViewerWrapper.tsx b/server/sonar-web/src/main/js/apps/code/components/SourceViewerWrapper.tsx index fd7bfe9a91d..76d669bd757 100644 --- a/server/sonar-web/src/main/js/apps/code/components/SourceViewerWrapper.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/SourceViewerWrapper.tsx @@ -54,6 +54,7 @@ export class SourceViewerWrapper extends React.PureComponent { component={component} highlightedLine={finalLine} onLoaded={this.scrollToLine} + showMeasures={true} /> ); } diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx index 071cdbf65e5..3628a9efefd 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx @@ -82,6 +82,7 @@ export interface Props { onIssueUnselect?: () => void; scroll?: (element: HTMLElement) => void; selectedIssue?: string; + showMeasures?: boolean; } interface State { @@ -710,7 +711,9 @@ export default class SourceViewerBase extends React.PureComponent {({ openComponent }) => ( )} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx index 385312ac53d..4f367fdf5ac 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx @@ -28,13 +28,7 @@ import ListIcon from '../icons-components/ListIcon'; import { ButtonIcon } from '../ui/buttons'; import { PopupPlacement } from '../ui/popups'; import { WorkspaceContextShape } from '../workspace/context'; -import { - getPathUrlAsString, - getBranchLikeUrl, - getComponentIssuesUrl, - getBaseUrl, - getCodeUrl -} from '../../helpers/urls'; +import { getPathUrlAsString, getBranchLikeUrl, getBaseUrl, getCodeUrl } from '../../helpers/urls'; import { collapsedDirFromPath, fileFromPath } from '../../helpers/path'; import { translate } from '../../helpers/l10n'; import { getBranchLikeQuery, isMainBranch } from '../../helpers/branches'; @@ -43,7 +37,9 @@ import { omitNil } from '../../helpers/request'; interface Props { branchLike: T.BranchLike | undefined; + issues?: T.Issue[]; openComponent: WorkspaceContextShape['openComponent']; + showMeasures?: boolean; sourceViewerFile: T.SourceViewerFile; } @@ -70,6 +66,7 @@ export default class SourceViewerHeader extends React.PureComponent -
+
+
+ {this.state.measuresOverlay && ( + + )} + + {showMeasures && ( +
+ {isUnitTest && ( +
+ + {translate('metric.tests.name')} + + + {formatMeasure(measures.tests, 'SHORT_INT')} + +
+ )} + + {!isUnitTest && ( +
+ + {translate('metric.lines.name')} + + + {formatMeasure(measures.lines, 'SHORT_INT')} + +
+ )} + + {measures.coverage !== undefined && ( +
+ + {translate('metric.coverage.name')} + + + {formatMeasure(measures.coverage, 'PERCENT')} + +
+ )} + + {measures.duplicationDensity !== undefined && ( +
+ + {translate('duplications')} + + + {formatMeasure(measures.duplicationDensity, 'PERCENT')} + +
+ )} + + {issues && issues.length > 0 && ( + <> +
+ + {['BUG', 'VULNERABILITY', 'CODE_SMELL', 'SECURITY_HOTSPOT'].map( + (type: T.IssueType) => { + const total = issues.filter(issue => issue.type === type).length; + return ( +
+ + {translate('issue.type', type)} + + + {formatMeasure(total, 'INT')} + +
+ ); + } + )} + + )} +
+ )} +
  • @@ -164,76 +238,6 @@ export default class SourceViewerHeader extends React.PureComponent - - {this.state.measuresOverlay && ( - - )} - -
    - {isUnitTest && ( -
    - - {formatMeasure(measures.tests, 'SHORT_INT')} - - - {translate('metric.tests.name')} - -
    - )} - - {!isUnitTest && ( -
    - - {formatMeasure(measures.lines, 'SHORT_INT')} - - - {translate('metric.lines.name')} - -
    - )} - -
    - - - {measures.issues != null ? formatMeasure(measures.issues, 'SHORT_INT') : 0} - - - - {translate('metric.violations.name')} - -
    - - {measures.coverage != null && ( -
    - - {formatMeasure(measures.coverage, 'PERCENT')} - - - {translate('metric.coverage.name')} - -
    - )} - - {measures.duplicationDensity != null && ( -
    - - {formatMeasure(measures.duplicationDensity, 'PERCENT')} - - - {translate('duplications')} - -
    - )} -
  • ); } diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerHeader-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerHeader-test.tsx new file mode 100644 index 00000000000..0d494a24e23 --- /dev/null +++ b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerHeader-test.tsx @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 * as React from 'react'; +import { shallow } from 'enzyme'; +import SourceViewerHeader from '../SourceViewerHeader'; +import { mockMainBranch, mockSourceViewerFile, mockIssue } from '../../../helpers/testMocks'; + +it('should render correctly for a regular file', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +it('should render correctly for a unit test', () => { + expect( + shallowRender({ + showMeasures: true, + sourceViewerFile: mockSourceViewerFile({ q: 'UTS', measures: { tests: '12' } }) + }) + ).toMatchSnapshot(); +}); + +it('should render correctly if issue details are passed', () => { + const issues = [ + mockIssue(false, { type: 'VULNERABILITY' }), + mockIssue(false, { type: 'VULNERABILITY' }), + mockIssue(false, { type: 'CODE_SMELL' }), + mockIssue(false, { type: 'SECURITY_HOTSPOT' }), + mockIssue(false, { type: 'SECURITY_HOTSPOT' }) + ]; + + expect( + shallowRender({ + issues, + showMeasures: true + }) + ).toMatchSnapshot(); + + expect( + shallowRender({ + issues, + showMeasures: false + }) + .find('.source-viewer-header-measure') + .exists() + ).toBe(false); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap new file mode 100644 index 00000000000..f5b7d5831f9 --- /dev/null +++ b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap @@ -0,0 +1,464 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly for a regular file 1`] = ` +
    +
    +
    + +
    + + + + foo/ + + + bar.ts + +
    +
    +
    + +
  • + + component_viewer.show_details + +
  • +
  • + + component_viewer.new_window + +
  • +
  • + + component_viewer.open_in_workspace + +
  • +
  • + + component_viewer.show_raw_source + +
  • + + } + overlayPlacement="bottom-right" + > + + + +
    +
    +`; + +exports[`should render correctly for a unit test 1`] = ` +
    +
    +
    + +
    + + + + foo/ + + + bar.ts + +
    +
    +
    +
    +
    + + metric.tests.name + + + 12 + +
    +
    + +
  • + + component_viewer.show_details + +
  • +
  • + + component_viewer.new_window + +
  • +
  • + + component_viewer.open_in_workspace + +
  • +
  • + + component_viewer.show_raw_source + +
  • + + } + overlayPlacement="bottom-right" + > + + + +
    +
    +`; + +exports[`should render correctly if issue details are passed 1`] = ` +
    +
    +
    + +
    + + + + foo/ + + + bar.ts + +
    +
    +
    +
    +
    + + metric.lines.name + + + 56 + +
    +
    + + metric.coverage.name + + + 85.2% + +
    +
    + + duplications + + + 1.0% + +
    +
    +
    + + issue.type.BUG + + + 0 + +
    +
    + + issue.type.VULNERABILITY + + + 2 + +
    +
    + + issue.type.CODE_SMELL + + + 1 + +
    +
    + + issue.type.SECURITY_HOTSPOT + + + 2 + +
    +
    + +
  • + + component_viewer.show_details + +
  • +
  • + + component_viewer.new_window + +
  • +
  • + + component_viewer.open_in_workspace + +
  • +
  • + + component_viewer.show_raw_source + +
  • + + } + overlayPlacement="bottom-right" + > + + + +
    +
    +`; 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 5b3843f3438..beb73998c2e 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/styles.css +++ b/server/sonar-web/src/main/js/components/SourceViewer/styles.css @@ -285,7 +285,6 @@ } .source-viewer-header-component { - float: left; padding-top: 4px; } @@ -304,19 +303,13 @@ border-bottom: none; } -.source-viewer-header-measures { - float: right; -} - .source-viewer-header-measures-scope { position: relative; float: left; } .source-viewer-header-measure { - display: inline-block; vertical-align: middle; - padding: 3px 0; font-size: var(--baseFontSize); } @@ -324,13 +317,18 @@ font-size: 18px; } +.source-viewer-header-measure-separator { + margin: 0 calc(3 * var(--gridSize)); + height: 30px; + border-right: 1px solid var(--gray80); +} + .source-viewer-header-measure + .source-viewer-header-measure { - margin-left: 25px; + margin-left: calc(3 * var(--gridSize)); } .source-viewer-header-measure-label { display: block; - margin-top: 4px; line-height: var(--smallFontSize); color: var(--secondFontColor); font-size: var(--smallFontSize); @@ -338,16 +336,16 @@ .source-viewer-header-measure-value { display: block; + margin-top: 2px; line-height: 18px; color: var(--baseFontColor); font-size: var(--bigFontSize); } .source-viewer-header-actions { - float: right; display: block; - margin-left: 25px; - padding: 8px 5px; + margin-left: calc(3 * var(--gridSize)); + padding: var(--gridSize) calc(var(--gridSize) / 2); } .source-viewer-header-actions svg { diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts index dd3b736a845..1cc767c2b09 100644 --- a/server/sonar-web/src/main/js/helpers/testMocks.ts +++ b/server/sonar-web/src/main/js/helpers/testMocks.ts @@ -441,6 +441,26 @@ export function mockShortLivingBranch( }; } +export function mockSourceViewerFile( + overrides: Partial = {} +): T.SourceViewerFile { + return { + key: 'foo', + measures: { + coverage: '85.2', + duplicationDensity: '1.0', + issues: '12', + lines: '56' + }, + path: 'foo/bar.ts', + project: 'my-project', + projectName: 'MyProject', + q: 'FIL', + uuid: 'foo-bar', + ...overrides + }; +} + export function mockLongLivingBranch( overrides: Partial = {} ): T.LongLivingBranch {