From f8d65ba2d9c01d19080e401e03eac12a55c4d4eb Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Thu, 23 Aug 2018 17:15:39 +0200 Subject: [PATCH] SONAR-11159 SONAR-11163 Add leak background color on code page --- .../src/main/js/app/styles/init/tables.css | 277 ++++++++++-------- server/sonar-web/src/main/js/app/theme.js | 1 + .../src/main/js/apps/code/components/App.tsx | 2 +- .../js/apps/code/components/Component.tsx | 6 +- .../js/apps/code/components/Components.tsx | 34 ++- .../apps/code/components/ComponentsEmpty.tsx | 10 +- .../apps/code/components/ComponentsHeader.tsx | 13 +- .../main/js/apps/code/components/Search.tsx | 3 +- .../components/__tests__/Components-test.tsx | 21 ++ .../__tests__/ComponentsHeader-test.tsx | 29 +- .../__snapshots__/Components-test.tsx.snap | 168 ++++++++++- .../ComponentsHeader-test.tsx.snap | 59 +++- .../sonar-web/src/main/js/apps/code/utils.ts | 16 +- .../main/js/components/charts/BubbleChart.tsx | 2 +- 14 files changed, 451 insertions(+), 190 deletions(-) diff --git a/server/sonar-web/src/main/js/app/styles/init/tables.css b/server/sonar-web/src/main/js/app/styles/init/tables.css index 19d4d93e8c3..c1a3b98f7d4 100644 --- a/server/sonar-web/src/main/js/app/styles/init/tables.css +++ b/server/sonar-web/src/main/js/app/styles/init/tables.css @@ -22,6 +22,79 @@ table { border-spacing: 0; } +table.form td { + padding: 2px 5px; + vertical-align: top; +} + +table.form th { + padding: 2px 5px; + font-weight: 600; +} + +table.form td.keyCell { + width: 1%; + white-space: nowrap; + text-align: right; + font-weight: bold; + vertical-align: top; +} + +table.form td img { + vertical-align: bottom; +} + +table.spaced th { + font-weight: bold; + color: #333; + padding: 4px 5px; +} + +table.spaced td { + padding: 3px 5px; + line-height: 18px; +} + +table.spaced td img { + vertical-align: text-bottom; +} + +table.spacedicon th { + font-weight: bold; + color: #333; + padding: 4px 5px; +} + +table.spacedicon td { + padding: 0 5px; + height: 24px; +} + +.thin { + width: 1%; +} + +.spacer { + width: 5px; + display: inline-block; +} + +.formError { + display: inline-block; + background-color: var(--orange); + color: #000; + padding: 0 5px; +} + +.admin hr { + background: transparent; + border-left: none; + border-right: none; + border-top: none; + border-bottom: 1px dashed #ffd324; + height: 1px; +} + .table > thead > tr > th { border-top: 0 none; font-weight: bold; @@ -46,6 +119,14 @@ table { color: var(--secondFontColor); } +.hoverable:hover { + background-color: var(--lightBlue); +} + +.hoverable:hover a { + color: #111; +} + .odd { background-color: #fff; } @@ -64,15 +145,35 @@ table { color: var(--baseFontColor); } +.table-cell-doc { + position: absolute; + z-index: var(--aboveNormalZIndex); + right: -8px; +} + +th > .table-cell-doc { + top: 50%; + margin-top: -6px; +} + +td.sep { + width: 10px; +} + +table.matrix tfoot td { + padding: 3px 5px; + line-height: 18px; +} + table.data, table.spaced { width: 100%; } -table.data > thead:after { - display: block; - line-height: 5px; - content: '\200C'; +table.data td.small, +table.data th.small { + padding: 0; + white-space: nowrap; } table.data > thead > tr > th { @@ -107,54 +208,6 @@ table.data > tbody > tr > td.text-middle { vertical-align: middle; } -table.data td.small, -table.data th.small { - padding: 0; - white-space: nowrap; -} - -table.data.zebra:not(.zebra-inversed) > tbody > tr:nth-child(even) { - background-color: #f5f5f5; -} - -table.data.zebra.zebra-inversed > tbody > tr:nth-child(odd) { - background-color: #f5f5f5; -} - -table.data.zebra-hover > tbody > tr:hover { - background-color: #ecf6fe !important; -} - -table.data.zebra > tbody > tr.selected { - background-color: #d9edf7 !important; -} - -table.data.condensed > tbody > tr > td { - padding-top: 5px; - padding-bottom: 5px; -} - -table.data tr.subheader th { - font-size: var(--smallFontSize); - border-bottom: none; -} - -table.data.no-outer-padding > thead > tr > th:first-child { - padding-left: 0; -} - -table.data.no-outer-padding > thead > tr > th:last-child { - padding-right: 0; -} - -table.data.no-outer-padding > tbody > tr > td:first-child { - padding-left: 0; -} - -table.data.no-outer-padding > tbody > tr > td:last-child { - padding-right: 0; -} - .data thead tr.total { background-color: var(--gray94); font-weight: normal; @@ -166,11 +219,18 @@ table.data.no-outer-padding > tbody > tr > td:last-child { } .data tr.blank, -.data tr.blank > td { +.data tr.blank > td, +.data td.blank { background-color: #fff !important; line-height: 15px; } +.data tr > th.leak, +.data tr > td.leak { + background-color: var(--leakColor) !important; + line-height: 15px; +} + .data tr.highlight { background-color: var(--lightBlue); } @@ -181,103 +241,72 @@ table.data.no-outer-padding > tbody > tr > td:last-child { vertical-align: middle; } -.hoverable:hover { - background-color: var(--lightBlue); -} - -.hoverable:hover a { - color: #111; -} - -table.spaced th { - font-weight: bold; - color: #333; - padding: 4px 5px; -} - -table.spaced td, -table.matrix tfoot td { - padding: 3px 5px; - line-height: 18px; +table.data.condensed > tbody > tr > td { + padding-top: 5px; + padding-bottom: 5px; } -table.spaced td img { - vertical-align: text-bottom; +table.data tr.subheader th { + font-size: var(--smallFontSize); + border-bottom: none; } -table.spacedicon th { - font-weight: bold; - color: #333; - padding: 4px 5px; +table.data:not(.boxed-padding) > thead:after { + display: block; + line-height: 5px; + content: '\200C'; } -table.spacedicon td { - padding: 0 5px; - height: 24px; +table.data.boxed-padding > thead > tr > th { + padding-top: 24px; } -.thin { - width: 1%; +table.data.boxed-padding > thead > tr > th:first-child, +table.data.boxed-padding > tbody > tr > td:first-child, +table.data.boxed-padding > thead > tr > th:last-child, +table.data.boxed-padding > tbody > tr > td:last-child { + width: 20px; + padding: 8px 0; } -td.sep { - width: 10px; +table.data.no-outer-padding > thead > tr > th:first-child, +table.data.no-outer-padding > tbody > tr > td:first-child { + padding-left: 0; } -.spacer { - width: 5px; - display: inline-block; +table.data.no-outer-padding > thead > tr > th:last-child, +table.data.no-outer-padding > tbody > tr > td:last-child { + padding-right: 0; } -.formError { - display: inline-block; - background-color: var(--orange); - color: #000; - padding: 0 5px; +table.data.boxed-padding > thead + tbody > tr:first-child > td { + padding-top: 16px; } -table.form td { - padding: 2px 5px; - vertical-align: top; +table.data.zebra-hover > tbody > tr:hover { + background-color: #ecf6fe !important; } -table.form th { - padding: 2px 5px; - font-weight: 600; +table.data.zebra > tbody > tr.selected { + background-color: #d9edf7 !important; } -table.form td.keyCell { - width: 1%; - white-space: nowrap; - text-align: right; - font-weight: bold; - vertical-align: top; +table.data.zebra:not(.zebra-inversed) > tbody > tr:nth-child(even) { + background-color: #f5f5f5; } -table.form td img { - vertical-align: bottom; +table.data.zebra.zebra-inversed > tbody > tr:nth-child(odd) { + background-color: #f5f5f5; } -.admin hr { - background: transparent; - border-left: none; - border-right: none; - border-top: none; - border-bottom: 1px dashed #ffd324; - height: 1px; +table.data.zebra:not(.zebra-inversed) + > tbody + > tr:not(.blank):nth-child(even) + > td:not(.blank).leak, +table.data.zebra.zebra-inversed > tbody > tr:not(.blank):nth-child(odd) > td:not(.blank).leak { + background-color: var(--leakColorHover) !important; } table#project-history tr > td { vertical-align: top; } - -.table-cell-doc { - position: absolute; - z-index: var(--aboveNormalZIndex); - right: -8px; -} - -th > .table-cell-doc { - top: 50%; - margin-top: -6px; -} diff --git a/server/sonar-web/src/main/js/app/theme.js b/server/sonar-web/src/main/js/app/theme.js index 0a90beef99b..bc1731dd834 100644 --- a/server/sonar-web/src/main/js/app/theme.js +++ b/server/sonar-web/src/main/js/app/theme.js @@ -51,6 +51,7 @@ module.exports = { secondFontColor: '#777', leakColor: '#fbf3d5', + leakColorHover: '#f0e7c4', leakBorderColor: '#eae3c7', snippetFontColor: '#f0f0f0', diff --git a/server/sonar-web/src/main/js/apps/code/components/App.tsx b/server/sonar-web/src/main/js/apps/code/components/App.tsx index e6579501d88..e4a25accdc5 100644 --- a/server/sonar-web/src/main/js/apps/code/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/App.tsx @@ -180,7 +180,7 @@ export class App extends React.PureComponent { const { loading, baseComponent, components, breadcrumbs, total, sourceViewer } = this.state; const shouldShowBreadcrumbs = breadcrumbs.length > 1; - const componentsClassName = classNames('boxed-group', 'boxed-group-inner', 'spacer-top', { + const componentsClassName = classNames('boxed-group', 'spacer-top', { 'new-loading': loading }); diff --git a/server/sonar-web/src/main/js/apps/code/components/Component.tsx b/server/sonar-web/src/main/js/apps/code/components/Component.tsx index e62b377faa7..81b80ea3591 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Component.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Component.tsx @@ -32,6 +32,7 @@ interface Props { branchLike?: BranchLike; canBrowse?: boolean; component: IComponentMeasure; + isLeak: boolean; metrics: Metric[]; previous?: IComponentMeasure; rootComponent: IComponentMeasure; @@ -77,6 +78,7 @@ export default class Component extends React.PureComponent { branchLike, canBrowse = false, component, + isLeak, metrics, previous, rootComponent, @@ -98,6 +100,7 @@ export default class Component extends React.PureComponent { return ( (this.node = node)}> + {componentAction} @@ -112,12 +115,13 @@ export default class Component extends React.PureComponent { {metrics.map(metric => ( - +
))} + ); } diff --git a/server/sonar-web/src/main/js/apps/code/components/Components.tsx b/server/sonar-web/src/main/js/apps/code/components/Components.tsx index 030bdfd188b..2a6bcf6e9f0 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Components.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Components.tsx @@ -18,11 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import * as classNames from 'classnames'; import Component from './Component'; import ComponentsEmpty from './ComponentsEmpty'; import ComponentsHeader from './ComponentsHeader'; import { BranchLike, ComponentMeasure, Metric } from '../../../app/types'; -import { getCodeMetrics } from '../utils'; +import { getCodeMetrics, showLeakMeasure } from '../utils'; interface Props { baseComponent?: ComponentMeasure; @@ -37,24 +38,33 @@ export default function Components(props: Props) { const { baseComponent, branchLike, components, rootComponent, selected } = props; const metricKeys = getCodeMetrics(rootComponent.qualifier, branchLike); const metrics = metricKeys.map(metric => props.metrics[metric]).filter(Boolean); + const isLeak = Boolean(baseComponent && showLeakMeasure(branchLike)); return ( - - +
+ {baseComponent && ( + + )} {baseComponent && ( - + + )} @@ -65,6 +75,7 @@ export default function Components(props: Props) { branchLike={branchLike} canBrowse={true} component={component} + isLeak={isLeak} key={component.key} metrics={metrics} previous={index > 0 ? list[index - 1] : undefined} @@ -73,8 +84,13 @@ export default function Components(props: Props) { /> )) ) : ( - + )} + + +
   + {' '} +  {' '} +
+ +
); diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentsEmpty.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentsEmpty.tsx index a82cc3e99b3..e87ba284337 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentsEmpty.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentsEmpty.tsx @@ -18,13 +18,17 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import * as classNames from 'classnames'; import { translate } from '../../../helpers/l10n'; -export default function ComponentsEmpty() { +export default function ComponentsEmpty({ isLeak }: { isLeak: boolean }) { return ( - {translate('no_results')} - + + + {translate('no_results')} + + ); } diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx index 2aeb89071a9..651de5704f0 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.tsx @@ -24,13 +24,14 @@ import { ComponentMeasure } from '../../../app/types'; interface Props { baseComponent?: ComponentMeasure; + isLeak: boolean; metrics: string[]; rootComponent: ComponentMeasure; } -const SHORT_NAME_METRICS = ['duplicated_lines_density']; +const SHORT_NAME_METRICS = ['duplicated_lines_density', 'new_lines', 'new_coverage']; -export default function ComponentsHeader({ baseComponent, metrics, rootComponent }: Props) { +export default function ComponentsHeader({ baseComponent, isLeak, metrics, rootComponent }: Props) { const isPortfolio = ['VW', 'SVW'].includes(rootComponent.qualifier); let columns: string[] = []; if (isPortfolio) { @@ -50,18 +51,20 @@ export default function ComponentsHeader({ baseComponent, metrics, rootComponent return ( -   -   + + {baseComponent && columns.map((column, index) => ( 0 + 'code-components-cell': index > 0, + leak: isLeak })} key={column}> {column} ))} + ); diff --git a/server/sonar-web/src/main/js/apps/code/components/Search.tsx b/server/sonar-web/src/main/js/apps/code/components/Search.tsx index 8129b592c16..291ff86d533 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Search.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Search.tsx @@ -186,7 +186,8 @@ export default class Search extends React.PureComponent { {loading && } {results && ( -
+
+
{ expect( @@ -44,6 +51,20 @@ it('renders correctly for a search', () => { ).toMatchSnapshot(); }); +it('renders correctly for leak', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); + it('handle no components correctly', () => { expect( shallow( diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentsHeader-test.tsx b/server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentsHeader-test.tsx index 4c7ab9f2414..21a2184df19 100644 --- a/server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentsHeader-test.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/ComponentsHeader-test.tsx @@ -28,7 +28,25 @@ const METRICS = ['foo', 'bar']; it('renders correctly for projects', () => { expect( shallow( - + + ) + ).toMatchSnapshot(); +}); + +it('renders correctly for leak', () => { + expect( + shallow( + ) ).toMatchSnapshot(); }); @@ -36,13 +54,18 @@ it('renders correctly for projects', () => { it('renders correctly for portfolios', () => { expect( shallow( - + ) ).toMatchSnapshot(); }); it('renders correctly for a search', () => { expect( - shallow() + shallow() ).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/Components-test.tsx.snap b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/Components-test.tsx.snap index 5c20c57810a..e7ad4a769a4 100644 --- a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/Components-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/Components-test.tsx.snap @@ -2,7 +2,7 @@ exports[`renders correctly 1`] = ` + @@ -78,6 +88,7 @@ exports[`renders correctly 1`] = ` "qualifier": "TRK", } } + isLeak={false} key="foo" metrics={ Array [ @@ -98,23 +109,91 @@ exports[`renders correctly 1`] = ` } selected={false} /> + +
  + +   + +
+ +
`; exports[`renders correctly for a search 1`] = ` + + + + + +
+ +
+`; + +exports[`renders correctly for leak 1`] = ` + + + + + + + + + +
+   + + +   + +
+ +
`; diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentsHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentsHeader-test.tsx.snap index 0d0a7b3c83d..a37d9e497f5 100644 --- a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentsHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentsHeader-test.tsx.snap @@ -7,12 +7,41 @@ exports[`renders correctly for a search 1`] = ` > + + + + +`; + +exports[`renders correctly for leak 1`] = ` + + + + + -   + metric.foo.name - -   + + metric.bar.name + `; @@ -24,12 +53,9 @@ exports[`renders correctly for portfolios 1`] = ` > -   - - -   - + colSpan={2} + /> + metric.ncloc.name + `; @@ -71,12 +100,9 @@ exports[`renders correctly for projects 1`] = ` > -   - - -   - + colSpan={2} + /> + metric.bar.name + `; diff --git a/server/sonar-web/src/main/js/apps/code/utils.ts b/server/sonar-web/src/main/js/apps/code/utils.ts index cd429f3d6fa..820ee070269 100644 --- a/server/sonar-web/src/main/js/apps/code/utils.ts +++ b/server/sonar-web/src/main/js/apps/code/utils.ts @@ -49,13 +49,7 @@ const PORTFOLIO_METRICS = [ 'ncloc' ]; -const LEAK_METRICS = [ - 'new_lines', - 'new_bugs', - 'new_vulnerabilities', - 'new_code_smells', - 'new_coverage' -]; +const LEAK_METRICS = ['new_lines', 'bugs', 'vulnerabilities', 'code_smells', 'new_coverage']; const PAGE_SIZE = 100; @@ -114,10 +108,6 @@ function expandRootDir(metrics: string[], branchLike?: BranchLike): ExpandRootDi }; } -function showLeakMeasure(branchLike?: BranchLike) { - return isShortLivingBranch(branchLike) || isPullRequest(branchLike); -} - function prepareChildren(r: any): Children { return { components: r.components, @@ -126,6 +116,10 @@ function prepareChildren(r: any): Children { }; } +export function showLeakMeasure(branchLike?: BranchLike) { + return isShortLivingBranch(branchLike) || isPullRequest(branchLike); +} + function skipRootDir(breadcrumbs: ComponentMeasure[]) { return breadcrumbs.filter(component => { return !(component.qualifier === 'DIR' && component.name === '/'); diff --git a/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx b/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx index 7810f4503d8..8d5b8c4f1ca 100644 --- a/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx +++ b/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx @@ -317,10 +317,10 @@ export default class BubbleChart extends React.Component, State> { return (