From 26e6aab3da5364bcb31fa8b23766610464a8781c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Thu, 23 Aug 2018 12:54:35 +0200 Subject: [PATCH] SONAR-11159 SONAR-11160 Add new coverage and overall coverage next to branch status --- .../src/main/js/components/QGWidget.tsx | 7 +- .../src/main/js/components/Widget.tsx | 9 +- server/sonar-web/src/main/js/api/measures.ts | 15 +-- .../js/app/components/ComponentContainer.tsx | 114 +++++++++++++----- .../__tests__/ComponentContainer-test.tsx | 45 +++++++ .../components/nav/component/ComponentNav.tsx | 9 +- .../nav/component/ComponentNavMeta.tsx | 16 ++- .../__tests__/ComponentNavMeta-test.tsx | 14 ++- .../ComponentNavMeta-test.tsx.snap | 29 ++++- .../src/main/js/app/styles/init/misc.css | 16 +++ server/sonar-web/src/main/js/app/types.ts | 1 + .../components/LeakPeriodLegend.tsx | 2 +- .../components/MeasureHeader.tsx | 2 +- .../LeakPeriodLegend-test.tsx.snap | 8 +- .../__snapshots__/MeasureHeader-test.tsx.snap | 4 +- .../sidebar/FacetMeasureValue.tsx | 4 +- .../FacetMeasureValue-test.tsx.snap | 2 +- .../main/js/apps/component-measures/style.css | 18 +-- .../apps/overview/components/OverviewApp.tsx | 8 +- .../js/components/common/BranchMeasures.tsx | 76 ++++++++++++ .../common/__tests__/BranchMeasures-test.tsx | 51 ++++++++ .../BranchMeasures-test.tsx.snap | 93 ++++++++++++++ .../main/js/components/nav/ContextNavBar.css | 2 +- .../main/js/components/ui/CoverageRating.tsx | 6 +- .../resources/org/sonar/l10n/core.properties | 2 + 25 files changed, 456 insertions(+), 97 deletions(-) create mode 100644 server/sonar-web/src/main/js/components/common/BranchMeasures.tsx create mode 100644 server/sonar-web/src/main/js/components/common/__tests__/BranchMeasures-test.tsx create mode 100644 server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchMeasures-test.tsx.snap diff --git a/server/sonar-vsts/src/main/js/components/QGWidget.tsx b/server/sonar-vsts/src/main/js/components/QGWidget.tsx index e4ecb754992..330ead0ec99 100644 --- a/server/sonar-vsts/src/main/js/components/QGWidget.tsx +++ b/server/sonar-vsts/src/main/js/components/QGWidget.tsx @@ -21,14 +21,14 @@ import * as React from 'react'; import * as classNames from 'classnames'; import SonarCloudIcon from './SonarCloudIcon'; import Tooltip from '../../../../../sonar-web/src/main/js/components/controls/Tooltip'; -import { MeasureComponent } from '../../../../../sonar-web/src/main/js/api/measures'; import { getPathUrlAsString, getProjectUrl } from '../../../../../sonar-web/src/main/js/helpers/urls'; +import { ComponentMeasure } from '../../../../../sonar-web/src/main/js/app/types'; interface Props { - component: MeasureComponent; + component: ComponentMeasure; } const QG_LEVELS: { [level: string]: string } = { @@ -39,7 +39,8 @@ const QG_LEVELS: { [level: string]: string } = { }; export default function QGWidget({ component }: Props) { - const qgMeasure = component && component.measures.find(m => m.metric === 'alert_status'); + const qgMeasure = + component && component.measures && component.measures.find(m => m.metric === 'alert_status'); if (!qgMeasure || !qgMeasure.value) { return

Project Quality Gate not computed.

; diff --git a/server/sonar-vsts/src/main/js/components/Widget.tsx b/server/sonar-vsts/src/main/js/components/Widget.tsx index 03655b0ccf5..049d8c45f9d 100644 --- a/server/sonar-vsts/src/main/js/components/Widget.tsx +++ b/server/sonar-vsts/src/main/js/components/Widget.tsx @@ -20,11 +20,8 @@ import * as React from 'react'; import QGWidget from './QGWidget'; import LoginForm from './LoginForm'; -import { - getMeasuresAndMeta, - MeasureComponent -} from '../../../../../sonar-web/src/main/js/api/measures'; -import { Metric } from '../../../../../sonar-web/src/main/js/app/types'; +import { getMeasuresAndMeta } from '../../../../../sonar-web/src/main/js/api/measures'; +import { Metric, ComponentMeasure } from '../../../../../sonar-web/src/main/js/app/types'; import { Settings } from '../utils'; interface Props { @@ -32,7 +29,7 @@ interface Props { } interface State { - component?: MeasureComponent; + component?: ComponentMeasure; loading: boolean; metrics?: Metric[]; unauthorized: boolean; diff --git a/server/sonar-web/src/main/js/api/measures.ts b/server/sonar-web/src/main/js/api/measures.ts index 378c5079242..9a5357a174a 100644 --- a/server/sonar-web/src/main/js/api/measures.ts +++ b/server/sonar-web/src/main/js/api/measures.ts @@ -20,8 +20,9 @@ import { getJSON, RequestData, postJSON, post } from '../helpers/request'; import throwGlobalError from '../app/utils/throwGlobalError'; import { - CustomMeasure, BranchParameters, + ComponentMeasure, + CustomMeasure, Measure, Metric, Paging, @@ -31,23 +32,15 @@ import { export function getMeasures( data: { componentKey: string; metricKeys: string } & BranchParameters -): Promise<{ metric: string; value?: string }[]> { +): Promise { return getJSON('/api/measures/component', data).then(r => r.component.measures, throwGlobalError); } -export interface MeasureComponent { - key: string; - description?: string; - measures: Measure[]; - name: string; - qualifier: string; -} - export function getMeasuresAndMeta( componentKey: string, metrics: string[], additional: RequestData = {} -): Promise<{ component: MeasureComponent; metrics?: Metric[]; periods?: Period[] }> { +): Promise<{ component: ComponentMeasure; metrics?: Metric[]; periods?: Period[] }> { const data = { ...additional, componentKey, metricKeys: metrics.join(',') }; return getJSON('/api/measures/component', data); } diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx index 98a8b23ad79..00bc81854f5 100644 --- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx @@ -23,11 +23,12 @@ import { connect } from 'react-redux'; import { differenceBy } from 'lodash'; import ComponentContainerNotFound from './ComponentContainerNotFound'; import ComponentNav from './nav/component/ComponentNav'; -import { Component, BranchLike } from '../types'; +import { Component, BranchLike, Measure } from '../types'; import handleRequiredAuthorization from '../utils/handleRequiredAuthorization'; import { getBranches, getPullRequests } from '../../api/branches'; import { Task, getTasksForComponent, PendingTask } from '../../api/ce'; import { getComponentData } from '../../api/components'; +import { getMeasures } from '../../api/measures'; import { getComponentNavigation } from '../../api/nav'; import { fetchOrganizations } from '../../store/rootActions'; import { STATUSES } from '../../apps/background-tasks/constants'; @@ -36,7 +37,8 @@ import { isBranch, isMainBranch, isLongLivingBranch, - isShortLivingBranch + isShortLivingBranch, + getBranchLikeQuery } from '../../helpers/branches'; interface Props { @@ -50,6 +52,7 @@ interface Props { interface State { branchLike?: BranchLike; branchLikes: BranchLike[]; + branchMeasures?: Measure[]; component?: Component; currentTask?: Task; isPending: boolean; @@ -114,46 +117,88 @@ export class ComponentContainer extends React.PureComponent { Promise.all([ getComponentNavigation({ componentKey: key, branch, pullRequest }), getComponentData({ component: key, branch, pullRequest }) - ]).then(([nav, data]) => { - const component = this.addQualifier({ ...nav, ...data }); + ]) + .then(([nav, data]) => { + const component = this.addQualifier({ ...nav, ...data }); - if (this.context.organizationsEnabled) { - this.props.fetchOrganizations([component.organization]); - } - - this.fetchBranches(component).then(({ branchLike, branchLikes }) => { + if (this.context.organizationsEnabled) { + this.props.fetchOrganizations([component.organization]); + } + return component; + }) + .then(this.fetchBranches) + .then(this.fetchBranchMeasures) + .then(({ branchLike, branchLikes, component, branchMeasures }) => { if (this.mounted) { - this.setState({ branchLike, branchLikes, component, loading: false }); + this.setState({ + branchLike, + branchLikes, + branchMeasures, + component, + loading: false + }); this.fetchStatus(component); } - }, onError); - }, onError); + }) + .catch(onError); } fetchBranches = ( component: Component - ): Promise<{ branchLike?: BranchLike; branchLikes: BranchLike[] }> => { + ): Promise<{ + branchLike?: BranchLike; + branchLikes: BranchLike[]; + component: Component; + }> => { const application = component.breadcrumbs.find(({ qualifier }) => qualifier === 'APP'); if (application) { return getBranches(application.key).then(branchLikes => { return { branchLike: this.getCurrentBranchLike(branchLikes), - branchLikes + branchLikes, + component }; }); } const project = component.breadcrumbs.find(({ qualifier }) => qualifier === 'TRK'); - return project - ? Promise.all([getBranches(project.key), getPullRequests(project.key)]).then( - ([branches, pullRequests]) => { - const branchLikes = [...branches, ...pullRequests]; - return { - branchLike: this.getCurrentBranchLike(branchLikes), - branchLikes - }; - } - ) - : Promise.resolve({ branchLikes: [] }); + if (project) { + return Promise.all([getBranches(project.key), getPullRequests(project.key)]).then( + ([branches, pullRequests]) => { + const branchLikes = [...branches, ...pullRequests]; + const branchLike = this.getCurrentBranchLike(branchLikes); + return { branchLike, branchLikes, component }; + } + ); + } + + return Promise.resolve({ branchLikes: [], component }); + }; + + fetchBranchMeasures = ({ + branchLike, + branchLikes, + component + }: { + branchLike: BranchLike; + branchLikes: BranchLike[]; + component: Component; + }): Promise<{ + branchLike?: BranchLike; + branchLikes: BranchLike[]; + branchMeasures?: Measure[]; + component: Component; + }> => { + const project = component.breadcrumbs.find(({ qualifier }) => qualifier === 'TRK'); + if (project && (isShortLivingBranch(branchLike) || isPullRequest(branchLike))) { + return getMeasures({ + componentKey: project.key, + metricKeys: 'coverage,new_coverage', + ...getBranchLikeQuery(branchLike) + }).then(measures => { + return { branchLike, branchLikes, branchMeasures: measures, component }; + }); + } + return Promise.resolve({ branchLike, branchLikes, component }); }; fetchStatus = (component: Component) => { @@ -259,14 +304,16 @@ export class ComponentContainer extends React.PureComponent { handleBranchesChange = () => { if (this.mounted && this.state.component) { - this.fetchBranches(this.state.component).then( - ({ branchLike, branchLikes }) => { - if (this.mounted) { - this.setState({ branchLike, branchLikes }); - } - }, - () => {} - ); + this.fetchBranches(this.state.component) + .then(this.fetchBranchMeasures) + .then( + ({ branchLike, branchLikes, branchMeasures }) => { + if (this.mounted) { + this.setState({ branchLike, branchLikes, branchMeasures }); + } + }, + () => {} + ); } }; @@ -286,6 +333,7 @@ export class ComponentContainer extends React.PureComponent { !['FIL', 'UTS'].includes(component.qualifier) && ( ({ getBranches: jest.fn().mockResolvedValue([]), @@ -48,6 +49,15 @@ jest.mock('../../../api/components', () => ({ getComponentData: jest.fn().mockResolvedValue({ analysisDate: '2018-07-30' }) })); +jest.mock('../../../api/measures', () => ({ + getMeasures: jest + .fn() + .mockResolvedValue([ + { metric: 'new_coverage', value: '0', periods: [{ index: 1, value: '95.9943' }] }, + { metric: 'coverage', value: '99.3' } + ]) +})); + jest.mock('../../../api/nav', () => ({ getComponentNavigation: jest.fn().mockResolvedValue({ breadcrumbs: [{ key: 'portfolioKey', name: 'portfolio', qualifier: 'VW' }], @@ -68,6 +78,7 @@ beforeEach(() => { (getComponentData as jest.Mock).mockClear(); (getComponentNavigation as jest.Mock).mockClear(); (getTasksForComponent as jest.Mock).mockClear(); + (getMeasures as jest.Mock).mockClear(); }); it('changes component', () => { @@ -144,6 +155,40 @@ it('updates branches on change', () => { expect(getPullRequests).toBeCalledWith('projectKey'); }); +it('updates the branch measures', async () => { + (getComponentNavigation as jest.Mock).mockResolvedValueOnce({ + breadcrumbs: [{ key: 'foo', name: 'Foo', qualifier: 'TRK' }], + key: 'foo' + }); + (getBranches as jest.Mock).mockResolvedValueOnce([ + { isMain: false, mergeBranch: 'master', name: 'feature', type: BranchType.SHORT } + ]); + (getPullRequests as jest.Mock).mockResolvedValueOnce([]); + const wrapper = shallow( + + + + ); + (wrapper.instance() as ComponentContainer).mounted = true; + wrapper.setState({ + branches: [{ isMain: true }], + component: { breadcrumbs: [{ key: 'foo', name: 'Foo', qualifier: 'TRK' }] }, + loading: false + }); + + await new Promise(setImmediate); + expect(getBranches).toBeCalledWith('foo'); + + await new Promise(setImmediate); + expect(getMeasures).toBeCalledWith({ + componentKey: 'foo', + metricKeys: 'coverage,new_coverage', + branch: 'feature' + }); +}); + it('loads organization', async () => { (getComponentData as jest.Mock).mockResolvedValueOnce({ organization: 'org' }); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx index 8f9d34a94fb..7024131f79b 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx @@ -24,7 +24,7 @@ import ComponentNavMenu from './ComponentNavMenu'; import ComponentNavBgTaskNotif from './ComponentNavBgTaskNotif'; import RecentHistory from '../../RecentHistory'; import * as theme from '../../../theme'; -import { BranchLike, Component } from '../../../types'; +import { BranchLike, Component, Measure } from '../../../types'; import ContextNavBar from '../../../../components/nav/ContextNavBar'; import { Task } from '../../../../api/ce'; import { STATUSES } from '../../../../apps/background-tasks/constants'; @@ -32,6 +32,7 @@ import './ComponentNav.css'; interface Props { branchLikes: BranchLike[]; + branchMeasures?: Measure[]; currentBranchLike: BranchLike | undefined; component: Component; currentTask?: Task; @@ -92,7 +93,11 @@ export default class ComponentNav extends React.PureComponent { // to close dropdown on any location change location={this.props.location} /> - + )} {(isShortLivingBranch(branchLike) || isPullRequest(branchLike)) && ( -
+
{isPullRequest(branchLike) && branchLike.url !== undefined && ( )} + {branchMeasures && + branchMeasures.length > 0 && ( + <> + + + + )}
)}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx index c6bff99c4a9..74ad5181300 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMeta-test.tsx @@ -22,7 +22,7 @@ import { shallow } from 'enzyme'; import { ComponentNavMeta } from '../ComponentNavMeta'; import { BranchType, ShortLivingBranch, LongLivingBranch, PullRequest } from '../../../../types'; -const component = { +const COMPONENT = { analysisDate: '2017-01-02T00:00:00.000Z', breadcrumbs: [], key: 'foo', @@ -32,6 +32,11 @@ const component = { version: '0.0.1' }; +const MEASURES = [ + { metric: 'new_coverage', value: '0', periods: [{ index: 1, value: '95.9943' }] }, + { metric: 'coverage', value: '99.3' } +]; + it('renders status of short-living branch', () => { const branch: ShortLivingBranch = { isMain: false, @@ -44,7 +49,8 @@ it('renders status of short-living branch', () => { shallow( ) @@ -62,7 +68,7 @@ it('renders meta for long-living branch', () => { shallow( ) @@ -82,7 +88,7 @@ it('renders meta for pull request', () => { shallow( ) diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMeta-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMeta-test.tsx.snap index 144ce956ea4..f618947a93d 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMeta-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMeta-test.tsx.snap @@ -38,7 +38,7 @@ exports[`renders meta for pull request 1`] = ` />
+ + + +
`; diff --git a/server/sonar-web/src/main/js/app/styles/init/misc.css b/server/sonar-web/src/main/js/app/styles/init/misc.css index 9b7c566d681..dcd5aff9e14 100644 --- a/server/sonar-web/src/main/js/app/styles/init/misc.css +++ b/server/sonar-web/src/main/js/app/styles/init/misc.css @@ -348,6 +348,16 @@ td.big-spacer-top { color: rgba(68, 68, 68, 0.3); } +.vertical-separator { + margin-left: calc(2 * var(--gridSize)); + margin-right: calc(2 * var(--gridSize)); +} + +.vertical-separator:after { + content: '|'; + color: var(--barBorderColor); +} + .capitalize { text-transform: capitalize !important; } @@ -389,3 +399,9 @@ td.big-spacer-top { background-color: var(--barBackgroundColor); color: inherit; } + +.leak-box { + background-color: var(--leakColor); + border: 1px solid var(--leakBorderColor); + padding: 4px 6px; +} diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 954eecdd305..254817b7430 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -108,6 +108,7 @@ export interface ComponentQualityProfile { interface ComponentMeasureIntern { branch?: string; + description?: string; isFavorite?: boolean; isRecentlyBrowsed?: boolean; key: string; diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx index f1d8747a2c8..57362e1933f 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx @@ -46,7 +46,7 @@ export default class LeakPeriodLegend extends React.PureComponent { render() { const { className, component, period } = this.props; - const leakClass = classNames('domain-measures-leak-header', className); + const leakClass = classNames('domain-measures-header leak-box', className); if (component.qualifier === 'APP') { return
{translate('issues.new_code_period')}
; } diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx index 2d524d52332..d85dcc4a067 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx @@ -56,7 +56,7 @@ export default function MeasureHeader(props: Props) { {isDiff ? (
overview.new_code_period_x.overview.period.previous_version.6,4
@@ -38,7 +38,7 @@ exports[`should render correctly 1`] = ` } >
overview.new_code_period_x.overview.period.previous_version.6,4
@@ -47,7 +47,7 @@ exports[`should render correctly 1`] = ` exports[`should render correctly 2`] = `
overview.new_code_period_x.overview.period.days.18
@@ -55,7 +55,7 @@ exports[`should render correctly 2`] = ` exports[`should render correctly for APP 1`] = `
issues.new_code_period
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap index c74f60aa659..7a4677c1b7c 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap @@ -94,7 +94,7 @@ exports[`should render correctly for leak 1`] = ` > +
{ additionalFields: 'metrics,periods', ...getBranchLikeQuery(branchLike) }).then( - r => { - if (this.mounted && r.metrics) { + ({ component, metrics, periods }) => { + if (this.mounted && metrics && component.measures) { this.setState({ loading: false, - measures: enhanceMeasuresWithMetrics(r.component.measures, r.metrics), - periods: r.periods + measures: enhanceMeasuresWithMetrics(component.measures, metrics), + periods }); } }, diff --git a/server/sonar-web/src/main/js/components/common/BranchMeasures.tsx b/server/sonar-web/src/main/js/components/common/BranchMeasures.tsx new file mode 100644 index 00000000000..eed1bf35e28 --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/BranchMeasures.tsx @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 * as classNames from 'classnames'; +import { Measure } from '../../app/types'; +import { getLeakValue } from '../measure/utils'; +import CoverageRating from '../ui/CoverageRating'; +import { formatMeasure, isDiffMetric } from '../../helpers/measures'; +import HelpTooltip from '../controls/HelpTooltip'; +import { translate } from '../../helpers/l10n'; + +interface Props { + measures: Measure[]; +} + +export default function BranchMeasures({ measures }: Props) { + const coverage = measures.find(measure => measure.metric === 'coverage'); + const newCoverage = measures.find(measure => measure.metric === 'new_coverage'); + if (!coverage && !newCoverage) { + return null; + } + + return ( +
+ {coverage && } + {newCoverage && ( + + )} +
+ ); +} + +interface MeasureProps { + className?: string; + measure: Measure; +} + +export function BranchCoverage({ className, measure }: MeasureProps) { + const isDiff = isDiffMetric(measure.metric); + const value = isDiff ? getLeakValue(measure) : measure.value; + return ( +
+ + {formatMeasure(value, 'PERCENT')} + +
+ ); +} diff --git a/server/sonar-web/src/main/js/components/common/__tests__/BranchMeasures-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/BranchMeasures-test.tsx new file mode 100644 index 00000000000..0f50823cc7a --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/__tests__/BranchMeasures-test.tsx @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 BranchMeasures, { BranchCoverage } from '../BranchMeasures'; + +const MEASURES = [ + { metric: 'new_coverage', value: '0', periods: [{ index: 1, value: '95.9943' }] }, + { metric: 'coverage', value: '99.3' } +]; + +describe('BranchMeasures', () => { + it('should render coverage measures', () => { + expect(shallow()).toMatchSnapshot(); + }); + + it('should render correctly when a coverage measure is missing', () => { + expect(shallow()).toMatchSnapshot(); + }); + + it('should not render anything', () => { + expect(shallow().type()).toBeNull(); + }); +}); + +describe('BranchCoverage', () => { + it('should render correctly', () => { + expect(shallow()).toMatchSnapshot(); + }); + + it('should render leak measure correctly', () => { + expect(shallow()).toMatchSnapshot(); + }); +}); diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchMeasures-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchMeasures-test.tsx.snap new file mode 100644 index 00000000000..153853dfec9 --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchMeasures-test.tsx.snap @@ -0,0 +1,93 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BranchCoverage should render correctly 1`] = ` +
+ + + 99.3% + + +
+`; + +exports[`BranchCoverage should render leak measure correctly 1`] = ` +
+ + + 96.0% + + +
+`; + +exports[`BranchMeasures should render correctly when a coverage measure is missing 1`] = ` +
+ +
+`; + +exports[`BranchMeasures should render coverage measures 1`] = ` +
+ + +
+`; diff --git a/server/sonar-web/src/main/js/components/nav/ContextNavBar.css b/server/sonar-web/src/main/js/components/nav/ContextNavBar.css index f4116968f6b..3dd7bd6ef40 100644 --- a/server/sonar-web/src/main/js/components/nav/ContextNavBar.css +++ b/server/sonar-web/src/main/js/components/nav/ContextNavBar.css @@ -84,7 +84,7 @@ .navbar-context-meta-secondary { position: absolute; - top: 36px; + top: 34px; right: 0; padding: 0 20px; white-space: nowrap; diff --git a/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx b/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx index 6814eea2f1d..f6cafaa72de 100644 --- a/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx +++ b/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx @@ -23,13 +23,13 @@ import * as theme from '../../app/theme'; const DonutChart = lazyLoad(() => import('../charts/DonutChart')); -const SIZE_TO_WIDTH_MAPPING = { small: 16, normal: 24, big: 40, huge: 60 }; +const SIZE_TO_WIDTH_MAPPING = { xs: 12, small: 16, normal: 24, big: 40, huge: 60 }; -const SIZE_TO_THICKNESS_MAPPING = { small: 2, normal: 3, big: 3, huge: 4 }; +const SIZE_TO_THICKNESS_MAPPING = { xs: 2, small: 2, normal: 3, big: 3, huge: 4 }; interface Props { muted?: boolean; - size?: 'small' | 'normal' | 'big' | 'huge'; + size?: 'xs' | 'small' | 'normal' | 'big' | 'huge'; value: number | string | null | undefined; } 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 efd5ce43150..c65ea7be512 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2817,6 +2817,8 @@ branches.short_lived.quality_gate.description=The branch status is passed becaus branches.short_lived_branches=Short-lived branches branches.pull_request.for_merge_into_x_from_y=for merge into {base} from {branch} branches.see_the_pr=See the PR +branches.measures.coverage.help=Estimated Overall Coverage after merge. +branches.measures.new_coverage.help=Coverage on New Code. See Measures for details. #------------------------------------------------------------------------------ -- 2.39.5