From 02546fd9c8dde8632678054726b4f0b0f28ea29b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Fri, 17 Aug 2018 09:54:44 +0200 Subject: [PATCH] SONAR-10471 Improve leak period description for specific date --- ...akPeriodLegend.js => LeakPeriodLegend.tsx} | 41 ++++--- ...gend-test.js => LeakPeriodLegend-test.tsx} | 21 +++- ...js.snap => LeakPeriodLegend-test.tsx.snap} | 22 ++++ ...akPeriodLegend.js => LeakPeriodLegend.tsx} | 100 +++++++----------- .../apps/overview/components/OverviewApp.tsx | 2 +- .../__tests__/LeakPeriodLegend-test.js | 67 ------------ .../__tests__/LeakPeriodLegend-test.tsx | 88 +++++++++++++++ .../LeakPeriodLegend-test.js.snap | 21 ---- .../LeakPeriodLegend-test.tsx.snap | 90 ++++++++++++++++ .../main/js/apps/overview/main/enhance.tsx | 4 +- .../sonar-web/src/main/js/helpers/periods.ts | 21 ++-- .../resources/org/sonar/l10n/core.properties | 8 +- 12 files changed, 302 insertions(+), 183 deletions(-) rename server/sonar-web/src/main/js/apps/component-measures/components/{LeakPeriodLegend.js => LeakPeriodLegend.tsx} (65%) rename server/sonar-web/src/main/js/apps/component-measures/components/__tests__/{LeakPeriodLegend-test.js => LeakPeriodLegend-test.tsx} (71%) rename server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/{LeakPeriodLegend-test.js.snap => LeakPeriodLegend-test.tsx.snap} (64%) rename server/sonar-web/src/main/js/apps/overview/components/{LeakPeriodLegend.js => LeakPeriodLegend.tsx} (59%) delete mode 100644 server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.js create mode 100644 server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/LeakPeriodLegend-test.js.snap create mode 100644 server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/LeakPeriodLegend-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.js b/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx similarity index 65% rename from server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.js rename to server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx index c2a322741c8..bed6f18b8b2 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx @@ -17,45 +17,56 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; -import classNames from 'classnames'; +import * as React from 'react'; +import * as classNames from 'classnames'; import DateFromNow from '../../../components/intl/DateFromNow'; import DateFormatter from '../../../components/intl/DateFormatter'; +import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import Tooltip from '../../../components/controls/Tooltip'; -import { getPeriodLabel, getPeriodDate } from '../../../helpers/periods'; +import { getPeriodLabel, getPeriodDate, Period, PeriodMode } from '../../../helpers/periods'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -/*:: import type { Component, Period } from '../types'; */ +import { differenceInDays } from '../../../helpers/dates'; +import { ComponentMeasure } from '../../../app/types'; -/*:: type Props = { - className?: string, - component: Component, - period: Period -}; */ +interface Props { + className?: string; + component: ComponentMeasure; + period: Period; +} -export default function LeakPeriodLegend({ className, component, period } /*: Props */) { +export default function LeakPeriodLegend({ className, component, period }: Props) { const leakClass = classNames('domain-measures-leak-header', className); if (component.qualifier === 'APP') { return
{translate('issues.new_code_period')}
; } + const leakPeriodLabel = getPeriodLabel(period); + if (!leakPeriodLabel) { + return null; + } + const label = (
- {translateWithParameters('overview.new_code_period_x', getPeriodLabel(period))} + {translateWithParameters('overview.new_code_period_x', leakPeriodLabel)}
); - if (period.mode === 'days') { + if (period.mode === PeriodMode.days) { return label; } const date = getPeriodDate(period); - const tooltip = ( + const tooltip = date && (
{', '} - + {differenceInDays(new Date(), date) < 1 ? ( + + ) : ( + + )}
); + return {label}; } diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.tsx similarity index 71% rename from server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.js rename to server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.tsx index 43e8ef11422..90ff902c1de 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/LeakPeriodLegend-test.tsx @@ -17,31 +17,41 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import LeakPeriodLegend from '../LeakPeriodLegend'; +import { PeriodMode } from '../../../../helpers/periods'; +import { differenceInDays } from '../../../../helpers/dates'; + +jest.mock('../../../../helpers/dates', () => { + const dates = require.requireActual('../../../../helpers/dates'); + dates.differenceInDays = jest.fn().mockReturnValue(10); + return dates; +}); const PROJECT = { key: 'foo', + name: 'Foo', qualifier: 'TRK' }; const APP = { key: 'bar', + name: 'Bar', qualifier: 'APP' }; const PERIOD = { date: '2017-05-16T13:50:02+0200', index: 1, - mode: 'previous_version', + mode: PeriodMode.previousVersion, parameter: '6,4' }; const PERIOD_DAYS = { date: '2017-05-16T13:50:02+0200', index: 1, - mode: 'days', + mode: PeriodMode.days, parameter: '18' }; @@ -53,3 +63,8 @@ it('should render correctly', () => { it('should render correctly for APP', () => { expect(shallow()).toMatchSnapshot(); }); + +it('should render a more precise date', () => { + (differenceInDays as jest.Mock).mockReturnValueOnce(0); + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/LeakPeriodLegend-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/LeakPeriodLegend-test.tsx.snap similarity index 64% rename from server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/LeakPeriodLegend-test.js.snap rename to server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/LeakPeriodLegend-test.tsx.snap index f5d95dbffd6..09d9f2e8e41 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/LeakPeriodLegend-test.js.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/LeakPeriodLegend-test.tsx.snap @@ -1,5 +1,27 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`should render a more precise date 1`] = ` + + + , + + + } +> +
+ overview.new_code_period_x.overview.period.previous_version.6,4 +
+
+`; + exports[`should render correctly 1`] = ` {translateWithParameters('overview.new_code_period_x', leakPeriodLabel)} @@ -83,20 +45,30 @@ export default function LeakPeriodLegend({ period } /*: { period: Period } */) { } const leakPeriodDate = getPeriodDate(period); - const tooltip = ( - - {formattedLeakPeriodDate => ( - - {translateWithParameters( - ['date'].includes(period.mode) - ? 'overview.last_analysis_on_x' - : 'overview.started_on_x', - formattedLeakPeriodDate - )} - + if (!leakPeriodDate) { + return null; + } + + const formattedDateFunction = (formattedLeakPeriodDate: string) => ( + + {translateWithParameters( + period.mode === PeriodMode.previousAnalysis + ? 'overview.previous_analysis_on_x' + : 'overview.started_on_x', + formattedLeakPeriodDate )} - + ); + + const tooltip = + differenceInDays(new Date(), leakPeriodDate) < 1 ? ( + {formattedDateFunction} + ) : ( + + {formattedDateFunction} + + ); + return (
@@ -106,7 +78,9 @@ export default function LeakPeriodLegend({ period } /*: { period: Period } */) { {fromNow => ( {translateWithParameters( - ['date'].includes(period.mode) ? 'overview.last_analysis_x' : 'overview.started_x', + period.mode === PeriodMode.previousAnalysis + ? 'overview.previous_analysis_x' + : 'overview.started_x', fromNow )} diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx index 63aed5899de..dc61e0ef7a8 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx @@ -160,7 +160,7 @@ export class OverviewApp extends React.PureComponent { getApplicationLeakPeriod = () => this.state.measures.find(measure => measure.metric.key === 'new_bugs') - ? { index: 1 } + ? ({ index: 1 } as Period) : undefined; isEmpty = () => diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.js b/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.js deleted file mode 100644 index 57ab92d22fc..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 React from 'react'; -import { shallow } from 'enzyme'; -import LeakPeriodLegend from '../LeakPeriodLegend'; - -describe('check note', () => { - it('10 days', () => { - const period = { - date: '2013-09-22T00:00:00+0200', - mode: 'days', - parameter: '10' - }; - expect(shallow()).toMatchSnapshot(); - }); - - it('date', () => { - const period = { - date: '2013-09-22T00:00:00+0200', - mode: 'date', - parameter: '2013-01-01' - }; - expect(shallow().find('DateFromNow')).toMatchSnapshot(); - }); - - it('version', () => { - const period = { - date: '2013-09-22T00:00:00+0200', - mode: 'version', - parameter: '0.1' - }; - expect(shallow().find('DateFromNow')).toMatchSnapshot(); - }); - - it('previous_version', () => { - const period = { - date: '2013-09-22T00:00:00+0200', - mode: 'previous_version' - }; - expect(shallow().find('DateFromNow')).toHaveLength(1); - }); - - it('previous_analysis', () => { - const period = { - date: '2013-09-22T00:00:00+0200', - mode: 'previous_analysis' - }; - expect(shallow().find('DateFromNow')).toHaveLength(1); - }); -}); diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx new file mode 100644 index 00000000000..77d51c4b0df --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/LeakPeriodLegend-test.tsx @@ -0,0 +1,88 @@ +/* + * 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 LeakPeriodLegend from '../LeakPeriodLegend'; +import { PeriodMode } from '../../../../helpers/periods'; +import { differenceInDays } from '../../../../helpers/dates'; + +jest.mock('../../../../helpers/dates', () => { + const dates = require.requireActual('../../../../helpers/dates'); + dates.differenceInDays = jest.fn().mockReturnValue(10); + return dates; +}); + +it('10 days', () => { + const period = { + date: '2013-09-22T00:00:00+0200', + index: 0, + mode: PeriodMode.days, + parameter: '10' + }; + expect(shallow()).toMatchSnapshot(); +}); + +it('date', () => { + const period = { + date: '2013-09-22T00:00:00+0200', + index: 0, + mode: PeriodMode.date, + parameter: '2013-01-01' + }; + expect(shallow()).toMatchSnapshot(); +}); + +it('version', () => { + const period = { + date: '2013-09-22T00:00:00+0200', + index: 0, + mode: PeriodMode.version, + parameter: '0.1' + }; + expect(shallow().find('.overview-legend')).toMatchSnapshot(); +}); + +it('previous_version', () => { + const period = { + date: '2013-09-22T00:00:00+0200', + index: 0, + mode: PeriodMode.previousVersion + }; + expect(shallow().find('.overview-legend')).toMatchSnapshot(); +}); + +it('previous_analysis', () => { + const period = { + date: '2013-09-22T00:00:00+0200', + index: 0, + mode: PeriodMode.previousAnalysis + }; + expect(shallow().find('.overview-legend')).toMatchSnapshot(); +}); + +it('should render a more precise date', () => { + (differenceInDays as jest.Mock).mockReturnValueOnce(0); + const period = { + date: '2018-08-17T00:00:00+0200', + index: 0, + mode: PeriodMode.previousVersion + }; + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/LeakPeriodLegend-test.js.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/LeakPeriodLegend-test.js.snap deleted file mode 100644 index afbce5c4a8a..00000000000 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/LeakPeriodLegend-test.js.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`check note 10 days 1`] = ` -
- overview.new_code_period_x.overview.period.days.10 -
-`; - -exports[`check note date 1`] = ` - -`; - -exports[`check note version 1`] = ` - -`; diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/LeakPeriodLegend-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/LeakPeriodLegend-test.tsx.snap new file mode 100644 index 00000000000..981c88be95d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/LeakPeriodLegend-test.tsx.snap @@ -0,0 +1,90 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`10 days 1`] = ` +
+ overview.new_code_period_x.overview.period.days.10 +
+`; + +exports[`date 1`] = ` + + [Function] + + } +> +
+ overview.new_code_period_x.overview.period.date.2013-01-01 +
+ +
+
+`; + +exports[`previous_analysis 1`] = ` +
+ overview.new_code_period_x.overview.period.previous_analysis. +
+ +
+`; + +exports[`previous_version 1`] = ` +
+ overview.new_code_period_x.overview.period.previous_version_only_date +
+ +
+`; + +exports[`should render a more precise date 1`] = ` + + [Function] + + } +> +
+ overview.new_code_period_x.overview.period.previous_version_only_date +
+ +
+
+`; + +exports[`version 1`] = ` +
+ overview.new_code_period_x.overview.period.version.0.1 +
+ +
+`; diff --git a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx index b5509cbd738..44aecab78af 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx +++ b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx @@ -33,7 +33,7 @@ import { getRatingTooltip } from '../../../helpers/measures'; import { getLocalizedMetricName } from '../../../helpers/l10n'; -import { getPeriodDate } from '../../../helpers/periods'; +import { getPeriodDate, Period } from '../../../helpers/periods'; import { getComponentDrilldownUrl, getComponentIssuesUrl, @@ -47,7 +47,7 @@ export interface EnhanceProps { branchLike?: BranchLike; component: Component; measures: MeasureEnhanced[]; - leakPeriod?: { index: number; date?: string }; + leakPeriod?: Period; history?: History; historyStartDate?: Date; } diff --git a/server/sonar-web/src/main/js/helpers/periods.ts b/server/sonar-web/src/main/js/helpers/periods.ts index afc0edf1c49..75838478729 100644 --- a/server/sonar-web/src/main/js/helpers/periods.ts +++ b/server/sonar-web/src/main/js/helpers/periods.ts @@ -20,15 +20,23 @@ import { translate, translateWithParameters } from './l10n'; import { parseDate } from './dates'; +export enum PeriodMode { + days = 'days', + date = 'date', + version = 'version', + previousAnalysis = 'previous_analysis', + previousVersion = 'previous_version' +} + export interface Period { date: string; index: number; - mode: string; + mode: PeriodMode; modeParam?: string; - parameter: string; + parameter?: string; } -export function getPeriod(periods: Period[] | undefined, index: number): Period | undefined { +export function getPeriod(periods: Period[] | undefined, index: number) { if (!Array.isArray(periods)) { return undefined; } @@ -36,22 +44,21 @@ export function getPeriod(periods: Period[] | undefined, index: number): Period return periods.find(period => period.index === index); } -export function getLeakPeriod(periods: Period[] | undefined): Period | undefined { +export function getLeakPeriod(periods: Period[] | undefined) { return getPeriod(periods, 1); } -export function getPeriodLabel(period: Period | undefined): string | undefined { +export function getPeriodLabel(period: Period | undefined) { if (!period) { return undefined; } const parameter = period.modeParam || period.parameter; - if (period.mode === 'previous_version' && !parameter) { return translate('overview.period.previous_version_only_date'); } - return translateWithParameters(`overview.period.${period.mode}`, parameter); + return translateWithParameters(`overview.period.${period.mode}`, parameter || ''); } export function getPeriodDate(period?: { date?: string }): Date | 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 e9dd6677ef6..796b4439143 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -898,7 +898,7 @@ property.error.notFloat=Not a floating point number property.error.notRegexp=Not a valid Java regular expression property.error.notInOptions=Not a valid option property.category.scm=SCM -property.sonar.leak.period.description=Period used to compare measures and track new issues. Values are:
  • Number of days before analysis, for example 5.
  • A custom date. Format is yyyy-MM-dd, for example 2010-12-25
  • 'previous_version' to compare to the previous version in the project history
  • A version, for example '1.2' or 'BASELINE'

When specifying a number of days or a date, the snapshot selected for comparison is the first one available inside the corresponding time range.

Changing this property only takes effect after subsequent project inspections.

+property.sonar.leak.period.description=Period used to compare measures and track new issues. Values are:

  • Number of days before analysis, for example 5.
  • A custom date. Format is yyyy-MM-dd, for example 2010-12-25
  • 'previous_version' to compare to the previous version in the project history
  • A version, for example '1.2' or 'BASELINE'

When specifying a number of days or a date, the snapshot selected as the baseline for comparison is the first one available inside the corresponding time range. Specifically, the first analysis in the range is considered to be before the leak period(/new code period).

Changing this property only takes effect after subsequent project inspections.

property.sonar.branch.longLivedBranches.regex.description=Regular expression used to detect whether a branch is a long living branch (as opposed to short living branch), based on its name. This applies only during first analysis, the type of a branch cannot be changed later. @@ -2357,9 +2357,9 @@ overview.quality_gate.ignored_conditions.tooltip=At the start of a new code peri overview.quality_profiles=Quality Profiles overview.new_code_period_x=New code: {0} overview.started_x=started {0} -overview.last_analysis_x=last analysis {0} +overview.previous_analysis_x=Previous analysis {0} overview.started_on_x=Started on {0} -overview.last_analysis_on_x=Last analysis on {0} +overview.previous_analysis_on_x=Previous analysis on {0} overview.on_new_code=On New Code overview.about_this_portfolio=About This Portfolio overview.about_this_project.APP=About This Application @@ -2407,7 +2407,7 @@ overview.period.previous_version_only_date=since previous version overview.period.previous_analysis=since previous analysis overview.period.days=last {0} days overview.period.version=since {0} -overview.period.date=since {0} +overview.period.date=after {0} overview.gate.ERROR=Failed overview.gate.WARN=Warning -- 2.39.5