diff options
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
55 files changed, 444 insertions, 427 deletions
diff --git a/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx index b81b2cf256f..cd5f6d39fea 100644 --- a/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx +++ b/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx @@ -18,35 +18,39 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as moment from 'moment'; import { sortBy } from 'lodash'; +import { FormattedRelative } from 'react-intl'; import { Link } from 'react-router'; import { Project } from './types'; +import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import Level from '../../../components/ui/Level'; +import Tooltip from '../../../components/controls/Tooltip'; import { translateWithParameters, translate } from '../../../helpers/l10n'; interface Props { project: Project; } -export default function ProjectCard(props: Props) { - const { project } = props; +export default function ProjectCard({ project }: Props) { const isAnalyzed = project.lastAnalysisDate != null; - const analysisMoment = isAnalyzed && moment(project.lastAnalysisDate); const links = sortBy(project.links, 'type'); return ( <div className="account-project-card clearfix"> <aside className="account-project-side"> {isAnalyzed - ? <div - className="account-project-analysis" - title={analysisMoment ? analysisMoment.format('LLL') : undefined}> - {translateWithParameters( - 'my_account.projects.analyzed_x', - analysisMoment ? analysisMoment.fromNow() : undefined - )} - </div> + ? <Tooltip + overlay={<DateTimeFormatter date={project.lastAnalysisDate} />} + placement="right"> + <div className="account-project-analysis"> + <FormattedRelative value={project.lastAnalysisDate}> + {(relativeDate: string) => + <span> + {translateWithParameters('my_account.projects.analyzed_x', relativeDate)} + </span>} + </FormattedRelative> + </div> + </Tooltip> : <div className="account-project-analysis"> {translate('my_account.projects.never_analyzed')} </div>} diff --git a/server/sonar-web/src/main/js/apps/account/projects/__tests__/ProjectCard-test.js b/server/sonar-web/src/main/js/apps/account/projects/__tests__/ProjectCard-test.js index f681f8af62f..aa597b1ce8c 100644 --- a/server/sonar-web/src/main/js/apps/account/projects/__tests__/ProjectCard-test.js +++ b/server/sonar-web/src/main/js/apps/account/projects/__tests__/ProjectCard-test.js @@ -49,9 +49,7 @@ it('should not render optional fields', () => { it('should render analysis date', () => { const project = { ...BASE, lastAnalysisDate: '2016-05-17' }; const output = shallow(<ProjectCard project={project} />); - expect(output.find('.account-project-analysis').text()).toContain( - 'my_account.projects.analyzed_x' - ); + expect(output.find('.account-project-analysis FormattedRelative')).toHaveLength(1); }); it('should not render analysis date', () => { diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js b/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js index 98bd76ece05..8fe7a69b028 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js @@ -17,11 +17,9 @@ * 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 $ from 'jquery'; -import moment from 'moment'; import React, { Component } from 'react'; -import { DATE_FORMAT } from '../constants'; +import { toShortNotSoISOString, isValidDate } from '../../../helpers/dates'; export default class DateFilter extends Component { componentDidMount() { @@ -47,17 +45,15 @@ export default class DateFilter extends Component { handleChange() { const date = {}; - const minDateRaw = this.refs.minDate.value; - const maxDateRaw = this.refs.maxDate.value; - const minDate = moment(minDateRaw, DATE_FORMAT, true); - const maxDate = moment(maxDateRaw, DATE_FORMAT, true); + const minDate = new Date(this.refs.minDate.value); + const maxDate = new Date(this.refs.maxDate.value); - if (minDate.isValid()) { - date.minSubmittedAt = minDate.format(DATE_FORMAT); + if (isValidDate(minDate)) { + date.minSubmittedAt = toShortNotSoISOString(minDate); } - if (maxDate.isValid()) { - date.maxExecutedAt = maxDate.format(DATE_FORMAT); + if (isValidDate(maxDate)) { + date.maxExecutedAt = toShortNotSoISOString(maxDate); } this.props.onChange(date); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Task.js b/server/sonar-web/src/main/js/apps/background-tasks/components/Task.js index 7f49e254a41..3c93857dadb 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/Task.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Task.js @@ -48,9 +48,9 @@ export default class Task extends React.PureComponent { <TaskComponent task={task} /> <TaskId task={task} /> <TaskDay task={task} prevTask={prevTask} /> - <TaskDate date={task.submittedAt} baseDate={task.submittedAt} format="LTS" /> - <TaskDate date={task.startedAt} baseDate={task.submittedAt} format="LTS" /> - <TaskDate date={task.executedAt} baseDate={task.submittedAt} format="LTS" /> + <TaskDate date={task.submittedAt} baseDate={task.submittedAt} /> + <TaskDate date={task.startedAt} baseDate={task.submittedAt} /> + <TaskDate date={task.executedAt} baseDate={task.submittedAt} /> <TaskExecutionTime task={task} /> <TaskActions component={component} diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.tsx index 117702bafd9..d03a3e62289 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.tsx @@ -17,28 +17,28 @@ * 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 moment from 'moment'; -import React from 'react'; +import * as React from 'react'; +import TimeFormatter from '../../../components/intl/TimeFormatter'; +import { differenceInDays, isValidDate } from '../../../helpers/dates'; -const TaskDate = ( - { date, baseDate, format } /*: { - date: string, - baseDate: string, - format: string -} */ -) => { - const m = moment(date); - const baseM = moment(baseDate); - const diff = date && baseDate ? m.diff(baseM, 'days') : 0; +interface Props { + date: string; + baseDate: string; +} + +export default function TaskDate({ date, baseDate }: Props) { + const parsedDate = new Date(date); + const parsedBaseDate = new Date(baseDate); + const diff = + date && baseDate && isValidDate(parsedDate) && isValidDate(parsedBaseDate) + ? differenceInDays(parsedDate, parsedBaseDate) + : 0; return ( <td className="thin nowrap text-right"> {diff > 0 && <span className="text-warning little-spacer-right">{`(+${diff}d)`}</span>} - {date ? moment(date).format(format) : ''} + {date && isValidDate(parsedDate) ? <TimeFormatter date={parsedDate} long={true} /> : ''} </td> ); -}; - -export default TaskDate; +} diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.tsx index 8307b341f36..ed6a79cef9c 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.tsx @@ -17,23 +17,23 @@ * 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 moment from 'moment'; -import React from 'react'; -/*:: import type { Task } from '../types'; */ +import * as React from 'react'; +import DateFormatter from '../../../components/intl/DateFormatter'; +import { isSameDay } from '../../../helpers/dates'; +import { ITask } from '../types'; -function isAnotherDay(a, b) { - return !moment(a).isSame(moment(b), 'day'); +interface Props { + task: ITask; + prevTask?: ITask; } -const TaskDay = ({ task, prevTask } /*: { task: Task, prevTask: ?Task } */) => { - const shouldDisplay = !prevTask || isAnotherDay(task.submittedAt, prevTask.submittedAt); +export default function TaskDay({ task, prevTask }: Props) { + const shouldDisplay = + !prevTask || !isSameDay(new Date(task.submittedAt), new Date(prevTask.submittedAt)); return ( <td className="thin nowrap text-right"> - {shouldDisplay ? moment(task.submittedAt).format('LL') : ''} + {shouldDisplay ? <DateFormatter date={task.submittedAt} long={true} /> : ''} </td> ); -}; - -export default TaskDay; +} diff --git a/server/sonar-web/src/main/js/apps/background-tasks/types.js b/server/sonar-web/src/main/js/apps/background-tasks/types.ts index e272937867f..c517f201399 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/types.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/types.ts @@ -17,9 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/*:: -export type Task = { - incremental: boolean, - id: string -}; -*/ + +export interface ITask { + incremental: boolean; + id: string; + submittedAt: string; +} 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.js index ef72a1d213b..e8b41e80e36 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.js @@ -20,7 +20,8 @@ // @flow import React from 'react'; import classNames from 'classnames'; -import moment from 'moment'; +import { FormattedRelative } from 'react-intl'; +import DateFormatter from '../../../components/intl/DateFormatter'; import Tooltip from '../../../components/controls/Tooltip'; import { getPeriodLabel, getPeriodDate } from '../../../helpers/periods'; import { translate, translateWithParameters } from '../../../helpers/l10n'; @@ -53,8 +54,13 @@ export default function LeakPeriodLegend({ className, component, period } /*: Pr } const date = getPeriodDate(period); - const fromNow = moment(date).fromNow(); - const tooltip = fromNow + ', ' + moment(date).format('LL'); + const tooltip = ( + <div> + <FormattedRelative value={date} /> + {', '} + <DateFormatter date={date} long={true} /> + </div> + ); return ( <Tooltip placement="left" overlay={tooltip}> {label} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js index b4c91f405cc..cf03a171d55 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js @@ -20,7 +20,6 @@ // @flow import React from 'react'; import classNames from 'classnames'; -import moment from 'moment'; import Breadcrumbs from './Breadcrumbs'; import FilesView from '../drilldown/FilesView'; import MeasureFavoriteContainer from './MeasureFavoriteContainer'; @@ -217,15 +216,13 @@ export default class MeasureContent extends React.PureComponent { renderCode() { const { component, leakPeriod } = this.props; const leakPeriodDate = - isDiffMetric(this.props.metric.key) && leakPeriod != null - ? moment(leakPeriod.date).toDate() - : null; + isDiffMetric(this.props.metric.key) && leakPeriod != null ? new Date(leakPeriod.date) : null; let filterLine; if (leakPeriodDate != null) { filterLine = line => { if (line.scmDate) { - const scmDate = moment(line.scmDate).toDate(); + const scmDate = new Date(line.scmDate); return scmDate >= leakPeriodDate; } else { return false; 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.js index 4f7fce011d7..9becda572f1 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.js @@ -45,12 +45,6 @@ const PERIOD_DAYS = { parameter: '18' }; -jest.mock('moment', () => () => ({ - format: () => 'March 1, 2017 9:36 AM', - fromNow: () => 'a month ago', - toDate: () => 'date' -})); - it('should render correctly', () => { expect(shallow(<LeakPeriodLegend component={PROJECT} period={PERIOD} />)).toMatchSnapshot(); expect(shallow(<LeakPeriodLegend component={PROJECT} period={PERIOD_DAYS} />)).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.js.snap index 0f0e328d85e..b82411aaf92 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.js.snap @@ -2,7 +2,19 @@ exports[`should render correctly 1`] = ` <Tooltip - overlay="a month ago, March 1, 2017 9:36 AM" + overlay={ + <div> + <FormattedRelative + updateInterval={10000} + value={2017-05-16T11:50:02.000Z} + /> + , + <DateFormatter + date={2017-05-16T11:50:02.000Z} + long={true} + /> + </div> + } placement="left" > <div diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js index 3a30ccd1ec4..8a409a34ac8 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js @@ -19,13 +19,16 @@ */ // @flow import React from 'react'; -import moment from 'moment'; import { max } from 'lodash'; +import { FormattedRelative, intlShape } from 'react-intl'; +import { formatterOption, longFormatterOption } from '../../../components/intl/DateFormatter'; +import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import FacetBox from '../../../components/facet/FacetBox'; import FacetHeader from '../../../components/facet/FacetHeader'; import FacetItem from '../../../components/facet/FacetItem'; import { BarChart } from '../../../components/charts/bar-chart'; import DateInput from '../../../components/controls/DateInput'; +import { isSameDay, toShortNotSoISOString } from '../../../helpers/dates'; import { translate } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; /*:: import type { Component } from '../utils'; */ @@ -46,8 +49,6 @@ type Props = {| |}; */ -const DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ssZZ'; - export default class CreationDateFacet extends React.PureComponent { /*:: props: Props; */ @@ -55,6 +56,10 @@ export default class CreationDateFacet extends React.PureComponent { open: true }; + static contextTypes = { + intl: intlShape + }; + property = 'createdAt'; hasValue = () => @@ -84,36 +89,34 @@ export default class CreationDateFacet extends React.PureComponent { }; handleBarClick = ( - { - createdAfter, - createdBefore - } /*: { - createdAfter: Object, - createdBefore?: Object + { createdAfter, createdBefore } /*: { + createdAfter: Date, + createdBefore?: Date } */ ) => { this.resetTo({ - createdAfter: createdAfter.format(DATE_FORMAT), - createdBefore: createdBefore && createdBefore.format(DATE_FORMAT) + createdAfter: toShortNotSoISOString(createdAfter), + createdBefore: createdBefore && toShortNotSoISOString(createdBefore) }); }; - handlePeriodChange = (property /*: string */) => (value /*: string */) => { + handlePeriodChange = (property /*: string */, value /*: string */) => { this.props.onChange({ createdAt: undefined, createdInLast: undefined, sinceLeakPeriod: undefined, - [property]: value + [property]: toShortNotSoISOString(new Date(value)) }); }; - handlePeriodClick = (period /*: string */) => { - this.resetTo({ createdInLast: period }); - }; + handlePeriodChangeBefore = (value /*: string */) => + this.handlePeriodChange('createdBefore', value); - handleLeakPeriodClick = () => { - this.resetTo({ sinceLeakPeriod: true }); - }; + handlePeriodChangeAfter = (value /*: string */) => this.handlePeriodChange('createdAfter', value); + + handlePeriodClick = (period /*: string */) => this.resetTo({ createdInLast: period }); + + handleLeakPeriodClick = () => this.resetTo({ sinceLeakPeriod: true }); renderBarChart() { const { createdBefore, stats } = this.props; @@ -128,31 +131,32 @@ export default class CreationDateFacet extends React.PureComponent { return null; } - const data = periods.map((startDate, index) => { - const startMoment = moment(startDate); - const nextStartMoment = - index < periods.length - 1 - ? moment(periods[index + 1]) - : createdBefore ? moment(createdBefore) : undefined; - const endMoment = nextStartMoment && nextStartMoment.clone().subtract(1, 'days'); + const { formatDate } = this.context.intl; + const beforeDate = createdBefore ? createdBefore : undefined; + const data = periods.map((start, index) => { + const startDate = new Date(start); + let nextStartDate = index < periods.length - 1 ? periods[index + 1] : beforeDate; + let endDate; + if (nextStartDate) { + nextStartDate = new Date(nextStartDate); + endDate = new Date(nextStartDate); + endDate.setDate(endDate.getDate() - 1); + } let tooltip = - formatMeasure(stats[startDate], 'SHORT_INT') + '<br>' + startMoment.format('LL'); - - if (endMoment) { - const isSameDay = endMoment.diff(startMoment, 'days') <= 1; - if (!isSameDay) { - tooltip += ' – ' + endMoment.format('LL'); - } + formatMeasure(stats[start], 'SHORT_INT') + + '<br/>' + + formatDate(startDate, longFormatterOption); + if (endDate && !isSameDay(endDate, startDate)) { + tooltip += ' – ' + formatDate(endDate, longFormatterOption); } return { - createdAfter: startMoment, - createdBefore: nextStartMoment, - startMoment, + createdAfter: startDate, + createdBefore: nextStartDate, tooltip, x: index, - y: stats[startDate] + y: stats[start] }; }); @@ -177,13 +181,12 @@ export default class CreationDateFacet extends React.PureComponent { } renderExactDate() { - const m = moment(this.props.createdAt); return ( <div className="search-navigator-facet-container"> - {m.format('LLL')} + <DateTimeFormatter date={this.props.createdAt} /> <br /> <span className="note"> - ({m.fromNow()}) + <FormattedRelative value={this.props.createdAt} /> </span> </div> ); @@ -191,26 +194,26 @@ export default class CreationDateFacet extends React.PureComponent { renderPeriodSelectors() { const { createdAfter, createdBefore } = this.props; - + const { formatDate } = this.context.intl; return ( <div className="search-navigator-date-facet-selection"> <DateInput className="search-navigator-date-facet-selection-dropdown-left" - onChange={this.handlePeriodChange('createdAfter')} + onChange={this.handlePeriodChangeAfter} placeholder={translate('from')} - value={createdAfter ? moment(createdAfter).format('YYYY-MM-DD') : undefined} + value={createdAfter ? formatDate(createdAfter, formatterOption) : undefined} /> <DateInput className="search-navigator-date-facet-selection-dropdown-right" - onChange={this.handlePeriodChange('createdBefore')} + onChange={this.handlePeriodChangeBefore} placeholder={translate('to')} - value={createdBefore ? moment(createdBefore).format('YYYY-MM-DD') : undefined} + value={createdBefore ? formatDate(createdBefore, formatterOption) : undefined} /> </div> ); } - renderPrefefinedPeriods() { + renderPredefinedPeriods() { const { component, createdInLast, sinceLeakPeriod } = this.props; return ( <div className="spacer-top issues-predefined-periods"> @@ -259,7 +262,7 @@ export default class CreationDateFacet extends React.PureComponent { : <div> {this.renderBarChart()} {this.renderPeriodSelectors()} - {this.renderPrefefinedPeriods()} + {this.renderPredefinedPeriods()} </div>; } diff --git a/server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.js b/server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.js index 3c9785aa8be..aaca5381cb4 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.js +++ b/server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.js @@ -20,7 +20,7 @@ // @flow import React from 'react'; import Tooltip from '../../../components/controls/Tooltip'; -import FormattedDate from '../../../components/ui/FormattedDate'; +import DateTooltipFormatter from '../../../components/intl/DateTooltipFormatter'; import { getApplicationLeak } from '../../../api/application'; import { translate } from '../../../helpers/l10n'; @@ -79,7 +79,7 @@ export default class ApplicationLeakPeriodLegend extends React.Component { ? <ul className="text-left"> {this.state.leaks.map(leak => <li key={leak.project}> - {leak.projectName}: <FormattedDate date={leak.date} format="LL" /> + {leak.projectName}: <DateTooltipFormatter date={leak.date} /> </li> )} </ul> diff --git a/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.js b/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.js index 88d26edef84..f932aeb8c8e 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.js +++ b/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.js @@ -19,7 +19,8 @@ */ // @flow import React from 'react'; -import moment from 'moment'; +import { FormattedRelative } from 'react-intl'; +import DateFormatter from '../../../components/intl/DateFormatter'; import Tooltip from '../../../components/controls/Tooltip'; import { getPeriodDate, getPeriodLabel } from '../../../helpers/periods'; import { translateWithParameters } from '../../../helpers/l10n'; @@ -82,23 +83,33 @@ export default function LeakPeriodLegend({ period } /*: { period: Period } */) { } const leakPeriodDate = getPeriodDate(period); - const momentDate = moment(leakPeriodDate); - const fromNow = momentDate.fromNow(); - const note = ['date'].includes(period.mode) - ? translateWithParameters('overview.last_analysis_x', fromNow) - : translateWithParameters('overview.started_x', fromNow); - const tooltip = ['date'].includes(period.mode) - ? translateWithParameters('overview.last_analysis_on_x', momentDate.format('LL')) - : translateWithParameters('overview.started_on_x', momentDate.format('LL')); - + const tooltip = ( + <DateFormatter date={leakPeriodDate} long={true}> + {formattedLeakPeriodDate => + <span> + {translateWithParameters( + ['date'].includes(period.mode) + ? 'overview.last_analysis_on_x' + : 'overview.started_on_x', + formattedLeakPeriodDate + )} + </span>} + </DateFormatter> + ); return ( <Tooltip overlay={tooltip} placement="top"> <div className="overview-legend"> {translateWithParameters('overview.leak_period_x', leakPeriodLabel)} <br /> - <span className="note"> - {note} - </span> + <FormattedRelative value={leakPeriodDate}> + {fromNow => + <span className="note"> + {translateWithParameters( + ['date'].includes(period.mode) ? 'overview.last_analysis_x' : 'overview.started_x', + fromNow + )} + </span>} + </FormattedRelative> </div> </Tooltip> ); diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js index 11d09882375..800a62d4ccd 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js +++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js @@ -20,7 +20,6 @@ // @flow import React from 'react'; import { uniq } from 'lodash'; -import moment from 'moment'; import QualityGate from '../qualityGate/QualityGate'; import ApplicationQualityGate from '../qualityGate/ApplicationQualityGate'; import BugsAndVulnerabilities from '../main/BugsAndVulnerabilities'; @@ -124,7 +123,7 @@ export default class OverviewApp extends React.PureComponent { const history /*: History */ = {}; r.measures.forEach(measure => { const measureHistory = measure.history.map(analysis => ({ - date: moment(analysis.date).toDate(), + date: new Date(analysis.date), value: analysis.value })); history[measure.metric] = measureHistory; 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 index 251e20594bc..b0179494ae0 100644 --- 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 @@ -37,7 +37,9 @@ describe('check note', () => { mode: 'date', parameter: '2013-01-01' }; - expect(shallow(<LeakPeriodLegend period={period} />).find('.note')).toMatchSnapshot(); + expect( + shallow(<LeakPeriodLegend period={period} />).find('FormattedRelative') + ).toMatchSnapshot(); }); it('version', () => { @@ -46,7 +48,9 @@ describe('check note', () => { mode: 'version', parameter: '0.1' }; - expect(shallow(<LeakPeriodLegend period={period} />).find('.note')).toMatchSnapshot(); + expect( + shallow(<LeakPeriodLegend period={period} />).find('FormattedRelative') + ).toMatchSnapshot(); }); it('previous_version', () => { @@ -54,7 +58,7 @@ describe('check note', () => { date: '2013-09-22T00:00:00+0200', mode: 'previous_version' }; - expect(shallow(<LeakPeriodLegend period={period} />).find('.note')).toMatchSnapshot(); + expect(shallow(<LeakPeriodLegend period={period} />).find('FormattedRelative')).toHaveLength(1); }); it('previous_analysis', () => { @@ -62,6 +66,6 @@ describe('check note', () => { date: '2013-09-22T00:00:00+0200', mode: 'previous_analysis' }; - expect(shallow(<LeakPeriodLegend period={period} />).find('.note')).toMatchSnapshot(); + expect(shallow(<LeakPeriodLegend period={period} />).find('FormattedRelative')).toHaveLength(1); }); }); diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/ApplicationLeakPeriodLegend-test.js.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/ApplicationLeakPeriodLegend-test.js.snap index 217405a6565..cb842597822 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/ApplicationLeakPeriodLegend-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/ApplicationLeakPeriodLegend-test.js.snap @@ -28,17 +28,15 @@ exports[`renders 2`] = ` <li> Foo : - <FormattedDate + <DateTooltipFormatter date="2017-01-01T11:39:03+0100" - format="LL" /> </li> <li> Bar : - <FormattedDate + <DateTooltipFormatter date="2017-02-01T11:39:03+0100" - format="LL" /> </li> </ul> 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 index 397016097b3..44caddb1324 100644 --- 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 @@ -9,33 +9,15 @@ exports[`check note 10 days 1`] = ` `; exports[`check note date 1`] = ` -<span - className="note" -> - overview.last_analysis_x.4 years ago -</span> -`; - -exports[`check note previous_analysis 1`] = ` -<span - className="note" -> - overview.started_x.4 years ago -</span> -`; - -exports[`check note previous_version 1`] = ` -<span - className="note" -> - overview.started_x.4 years ago -</span> +<FormattedRelative + updateInterval={10000} + value={2013-09-21T22:00:00.000Z} +/> `; exports[`check note version 1`] = ` -<span - className="note" -> - overview.started_x.4 years ago -</span> +<FormattedRelative + updateInterval={10000} + value={2013-09-21T22:00:00.000Z} +/> `; diff --git a/server/sonar-web/src/main/js/apps/overview/events/Analysis.js b/server/sonar-web/src/main/js/apps/overview/events/Analysis.js index ef1b6d37e8d..5a9f7cc7807 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/Analysis.js +++ b/server/sonar-web/src/main/js/apps/overview/events/Analysis.js @@ -21,7 +21,7 @@ import React from 'react'; import { sortBy } from 'lodash'; import Event from './Event'; -import FormattedDate from '../../../components/ui/FormattedDate'; +import DateTooltipFormatter from '../../../components/intl/DateTooltipFormatter'; import { translate } from '../../../helpers/l10n'; /*:: import type { Analysis as AnalysisType, Event as EventType } from '../../projectActivity/types'; */ @@ -46,7 +46,7 @@ export default function Analysis(props /*: Props */) { <li className="overview-analysis"> <div className="small little-spacer-bottom"> <strong> - <FormattedDate date={analysis.date} format="LL" /> + <DateTooltipFormatter date={analysis.date} placement="right" /> </strong> </div> diff --git a/server/sonar-web/src/main/js/apps/overview/events/Event.js b/server/sonar-web/src/main/js/apps/overview/events/Event.js index be23bc7bc07..bf62a5bd1cc 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/Event.js +++ b/server/sonar-web/src/main/js/apps/overview/events/Event.js @@ -20,8 +20,8 @@ // @flow import React from 'react'; import Tooltip from '../../../components/controls/Tooltip'; -/*:: import type { Event as EventType } from '../../projectActivity/types'; */ import { translate } from '../../../helpers/l10n'; +/*:: import type { Event as EventType } from '../../projectActivity/types'; */ export default function Event(props /*: { event: EventType } */) { const { event } = props; @@ -35,13 +35,17 @@ export default function Event(props /*: { event: EventType } */) { } return ( - <div className="overview-analysis-event"> + <span className="overview-analysis-event"> <span className="note">{translate('event.category', event.category)}:</span>{' '} - <Tooltip overlay={event.description} placement="left"> - <strong> - {event.name} - </strong> - </Tooltip> - </div> + {event.description + ? <Tooltip overlay={event.description} placement="left" mouseEnterDelay={0.5}> + <strong> + {event.name} + </strong> + </Tooltip> + : <strong> + {event.name} + </strong>} + </span> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltips.js b/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltips.js index d4bc3609a6d..c1a898a3bf1 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltips.js +++ b/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltips.js @@ -19,7 +19,7 @@ */ import React from 'react'; import BubblePopup from '../../../components/common/BubblePopup'; -import FormattedDate from '../../../components/ui/FormattedDate'; +import DateFormatter from '../../../components/intl/DateFormatter'; import PreviewGraphTooltipsContent from './PreviewGraphTooltipsContent'; /*:: import type { Metric } from '../types'; */ /*:: import type { Serie } from '../../../components/charts/AdvancedTimeline'; */ @@ -56,7 +56,7 @@ export default class PreviewGraphTooltips extends React.PureComponent { <BubblePopup customClass={customClass} position={{ top, left, width: TOOLTIP_WIDTH }}> <div className="overview-analysis-graph-tooltip"> <div className="overview-analysis-graph-tooltip-title"> - <FormattedDate date={this.props.selectedDate} format="LL" /> + <DateFormatter date={this.props.selectedDate} long={true} /> </div> <table className="width-100"> <tbody> diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.js.snap b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.js.snap index e37d21f47d9..33bc4e28c18 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.js.snap @@ -8,9 +8,9 @@ exports[`should sort the events with version first 1`] = ` className="small little-spacer-bottom" > <strong> - <FormattedDate + <DateTooltipFormatter date="2017-06-10T16:10:59+0200" - format="LL" + placement="right" /> </strong> </div> diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Event-test.js.snap b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Event-test.js.snap index 18c6f76ec73..802c927a96c 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Event-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Event-test.js.snap @@ -9,7 +9,7 @@ exports[`should render a version correctly 1`] = ` `; exports[`should render an event correctly 1`] = ` -<div +<span className="overview-analysis-event" > <span @@ -19,12 +19,8 @@ exports[`should render an event correctly 1`] = ` : </span> - <Tooltip - placement="left" - > - <strong> - test - </strong> - </Tooltip> -</div> + <strong> + test + </strong> +</span> `; diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap index 0d0aec9012c..64d9d39a3e4 100644 --- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap @@ -17,9 +17,9 @@ exports[`should render correctly 1`] = ` <div className="overview-analysis-graph-tooltip-title" > - <FormattedDate + <DateFormatter date={2011-10-25T10:27:41.000Z} - format="LL" + long={true} /> </div> <table diff --git a/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js b/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js index 00e81e9d47a..2eab616e195 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js +++ b/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js @@ -17,10 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import moment from 'moment'; import React from 'react'; import { Link } from 'react-router'; +import { FormattedRelative } from 'react-intl'; import Tooltip from '../../../components/controls/Tooltip'; +import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import enhance from './enhance'; import { getMetricName } from '../helpers/metrics'; import { translate, translateWithParameters } from '../../../helpers/l10n'; @@ -43,9 +44,14 @@ class CodeSmells extends React.PureComponent { Object.assign(params, { sinceLeakPeriod: 'true' }); } - const formattedAnalysisDate = moment(component.analysisDate).format('LLL'); - const tooltip = translateWithParameters('widget.as_calculated_on_x', formattedAnalysisDate); - + const tooltip = ( + <DateTimeFormatter date={component.analysisDate}> + {formattedAnalysisDate => + <span> + {translateWithParameters('widget.as_calculated_on_x', formattedAnalysisDate)} + </span>} + </DateTimeFormatter> + ); return ( <Tooltip overlay={tooltip} placement="top"> <Link to={getComponentIssuesUrl(component.key, params)}> @@ -56,12 +62,16 @@ class CodeSmells extends React.PureComponent { } renderTimelineStartDate() { - const momentDate = moment(this.props.historyStartDate); - const fromNow = momentDate.fromNow(); + if (!this.props.historyStartDate) { + return null; + } return ( - <span className="overview-domain-timeline-date"> - {translateWithParameters('overview.started_x', fromNow)} - </span> + <FormattedRelative value={this.props.historyStartDate}> + {fromNow => + <span className="overview-domain-timeline-date"> + {translateWithParameters('overview.started_x', fromNow)} + </span>} + </FormattedRelative> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/main/enhance.js b/server/sonar-web/src/main/js/apps/overview/main/enhance.js index b4be68adcb1..fb008cabb12 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/enhance.js +++ b/server/sonar-web/src/main/js/apps/overview/main/enhance.js @@ -19,9 +19,9 @@ */ import React from 'react'; import { Link } from 'react-router'; -import moment from 'moment'; import { DrilldownLink } from '../../../components/shared/drilldown-link'; import BubblesIcon from '../../../components/icons-components/BubblesIcon'; +import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import HistoryIcon from '../../../components/icons-components/HistoryIcon'; import Rating from './../../../components/ui/Rating'; import Timeline from '../components/Timeline'; @@ -157,8 +157,16 @@ export default function enhance(ComposedComponent) { if (isDiffMetric(metric)) { Object.assign(params, { sinceLeakPeriod: 'true' }); } - const formattedAnalysisDate = moment(component.analysisDate).format('LLL'); - const tooltip = translateWithParameters('widget.as_calculated_on_x', formattedAnalysisDate); + + const tooltip = ( + <DateTimeFormatter date={component.analysisDate}> + {formattedAnalysisDate => + <span> + {translateWithParameters('widget.as_calculated_on_x', formattedAnalysisDate)} + </span>} + </DateTimeFormatter> + ); + return ( <Tooltip overlay={tooltip} placement="top"> <Link to={getComponentIssuesUrl(component.key, params)}> diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.js.snap index 06b8994c23d..1bc54255ce1 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.js.snap @@ -4,11 +4,11 @@ exports[`generateCoveredLinesMetric should correctly generate covered lines metr Object { "data": Array [ Object { - "x": 2017-04-27T06:21:32.000Z, + "x": 2017-04-27T08:21:32.000Z, "y": 88, }, Object { - "x": 2017-04-30T21:06:24.000Z, + "x": 2017-04-30T23:06:24.000Z, "y": 50, }, ], @@ -23,11 +23,11 @@ Array [ Object { "data": Array [ Object { - "x": 2017-04-27T06:21:32.000Z, + "x": 2017-04-27T08:21:32.000Z, "y": 88, }, Object { - "x": 2017-04-30T21:06:24.000Z, + "x": 2017-04-30T23:06:24.000Z, "y": 50, }, ], @@ -38,11 +38,11 @@ Array [ Object { "data": Array [ Object { - "x": 2017-04-27T06:21:32.000Z, + "x": 2017-04-27T08:21:32.000Z, "y": 100, }, Object { - "x": 2017-04-30T21:06:24.000Z, + "x": 2017-04-30T23:06:24.000Z, "y": 100, }, ], @@ -57,9 +57,9 @@ exports[`getAnalysesByVersionByDay should also filter analysis based on the quer Array [ Object { "byDay": Object { - "2017-4-18": Array [ + "1495065600000": Array [ Object { - "date": 2017-05-18T12:13:07.000Z, + "date": 2017-05-18T14:13:07.000Z, "events": Array [ Object { "category": "QUALITY_PROFILE", @@ -76,9 +76,9 @@ Array [ }, Object { "byDay": Object { - "2017-4-16": Array [ + "1494892800000": Array [ Object { - "date": 2017-05-16T05:09:59.000Z, + "date": 2017-05-16T07:09:59.000Z, "events": Array [ Object { "category": "VERSION", @@ -105,9 +105,9 @@ exports[`getAnalysesByVersionByDay should also filter analysis based on the quer Array [ Object { "byDay": Object { - "2017-5-9": Array [ + "1496966400000": Array [ Object { - "date": 2017-06-09T09:12:27.000Z, + "date": 2017-06-09T11:12:27.000Z, "events": Array [], "key": "AVyM9n3cHjR_PLDzRciT", }, @@ -118,9 +118,9 @@ Array [ }, Object { "byDay": Object { - "2017-4-18": Array [ + "1495065600000": Array [ Object { - "date": 2017-05-18T12:13:07.000Z, + "date": 2017-05-18T14:13:07.000Z, "events": Array [ Object { "category": "QUALITY_PROFILE", @@ -131,9 +131,9 @@ Array [ "key": "AVxZtCpH7841nF4RNEMI", }, ], - "2017-5-9": Array [ + "1496966400000": Array [ Object { - "date": 2017-06-09T09:12:27.000Z, + "date": 2017-06-09T11:12:27.000Z, "events": Array [ Object { "category": "VERSION", @@ -160,9 +160,9 @@ exports[`getAnalysesByVersionByDay should correctly map analysis by versions and Array [ Object { "byDay": Object { - "2017-5-9": Array [ + "1496966400000": Array [ Object { - "date": 2017-06-09T11:06:10.000Z, + "date": 2017-06-09T13:06:10.000Z, "events": Array [ Object { "category": "VERSION", @@ -173,7 +173,7 @@ Array [ "key": "AVyMjlK1HjR_PLDzRbB9", }, Object { - "date": 2017-06-09T09:12:27.000Z, + "date": 2017-06-09T11:12:27.000Z, "events": Array [], "key": "AVyM9n3cHjR_PLDzRciT", }, @@ -184,9 +184,9 @@ Array [ }, Object { "byDay": Object { - "2017-4-18": Array [ + "1495065600000": Array [ Object { - "date": 2017-05-18T12:13:07.000Z, + "date": 2017-05-18T14:13:07.000Z, "events": Array [ Object { "category": "QUALITY_PROFILE", @@ -197,14 +197,14 @@ Array [ "key": "AVxZtCpH7841nF4RNEMI", }, Object { - "date": 2017-05-18T05:17:32.000Z, + "date": 2017-05-18T07:17:32.000Z, "events": Array [], "key": "AVwaa1qkpbBde8B6UhYI", }, ], - "2017-5-9": Array [ + "1496966400000": Array [ Object { - "date": 2017-06-09T09:12:27.000Z, + "date": 2017-06-09T11:12:27.000Z, "events": Array [ Object { "category": "VERSION", @@ -221,9 +221,16 @@ Array [ }, Object { "byDay": Object { - "2017-4-16": Array [ + "1494288000000": Array [ Object { - "date": 2017-05-16T05:09:59.000Z, + "date": 2017-05-09T12:03:59.000Z, + "events": Array [], + "key": "AVvtGF3IY6vCuQNDdwxI", + }, + ], + "1494892800000": Array [ + Object { + "date": 2017-05-16T07:09:59.000Z, "events": Array [ Object { "category": "VERSION", @@ -239,13 +246,6 @@ Array [ "key": "AVwQF7kwl-nNFgFWOJ3V", }, ], - "2017-4-9": Array [ - Object { - "date": 2017-05-09T10:03:59.000Z, - "events": Array [], - "key": "AVvtGF3IY6vCuQNDdwxI", - }, - ], }, "key": "AVyM9oI1HjR_PLDzRciU", "version": "1.0", @@ -257,26 +257,26 @@ exports[`getAnalysesByVersionByDay should create fake version 1`] = ` Array [ Object { "byDay": Object { - "2017-4-18": Array [ + "1495065600000": Array [ Object { - "date": 2017-05-18T12:13:07.000Z, + "date": 2017-05-18T14:13:07.000Z, "events": Array [], "key": "AVxZtCpH7841nF4RNEMI", }, ], - "2017-5-9": Array [ + "1496966400000": Array [ Object { - "date": 2017-06-09T11:06:10.000Z, + "date": 2017-06-09T13:06:10.000Z, "events": Array [], "key": "AVyMjlK1HjR_PLDzRbB9", }, Object { - "date": 2017-06-09T09:12:27.000Z, + "date": 2017-06-09T11:12:27.000Z, "events": Array [], "key": "AVyM9n3cHjR_PLDzRciT", }, Object { - "date": 2017-06-09T09:12:27.000Z, + "date": 2017-06-09T11:12:27.000Z, "events": Array [], "key": "AVyMjlK1HjR_PLDzRbB9", }, diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js index 2ee3b2cfe3d..455c679d2d7 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js @@ -19,22 +19,23 @@ */ // @flow import * as utils from '../utils'; +import * as dates from '../../../helpers/dates'; const ANALYSES = [ { key: 'AVyMjlK1HjR_PLDzRbB9', - date: new Date('2017-06-09T13:06:10+0200'), + date: new Date('2017-06-09T13:06:10.000Z'), events: [{ key: 'AVyM9oI1HjR_PLDzRciU', category: 'VERSION', name: '1.1-SNAPSHOT' }] }, - { key: 'AVyM9n3cHjR_PLDzRciT', date: new Date('2017-06-09T11:12:27+0200'), events: [] }, + { key: 'AVyM9n3cHjR_PLDzRciT', date: new Date('2017-06-09T11:12:27.000Z'), events: [] }, { key: 'AVyMjlK1HjR_PLDzRbB9', - date: new Date('2017-06-09T11:12:27+0200'), + date: new Date('2017-06-09T11:12:27.000Z'), events: [{ key: 'AVyM9oI1HjR_PLDzRciU', category: 'VERSION', name: '1.1' }] }, { key: 'AVxZtCpH7841nF4RNEMI', - date: new Date('2017-05-18T14:13:07+0200'), + date: new Date('2017-05-18T14:13:07.000Z'), events: [ { key: 'AVxZtC-N7841nF4RNEMJ', @@ -43,10 +44,10 @@ const ANALYSES = [ } ] }, - { key: 'AVwaa1qkpbBde8B6UhYI', date: new Date('2017-05-18T07:17:32+0200'), events: [] }, + { key: 'AVwaa1qkpbBde8B6UhYI', date: new Date('2017-05-18T07:17:32.000Z'), events: [] }, { key: 'AVwQF7kwl-nNFgFWOJ3V', - date: new Date('2017-05-16T07:09:59+0200'), + date: new Date('2017-05-16T07:09:59.000Z'), events: [ { key: 'AVyM9oI1HjR_PLDzRciU', category: 'VERSION', name: '1.0' }, { @@ -56,22 +57,22 @@ const ANALYSES = [ } ] }, - { key: 'AVvtGF3IY6vCuQNDdwxI', date: new Date('2017-05-09T12:03:59+0200'), events: [] } + { key: 'AVvtGF3IY6vCuQNDdwxI', date: new Date('2017-05-09T12:03:59.000Z'), events: [] } ]; const HISTORY = [ { metric: 'lines_to_cover', history: [ - { date: new Date('2017-04-27T08:21:32+0200'), value: '100' }, - { date: new Date('2017-04-30T23:06:24+0200'), value: '100' } + { date: new Date('2017-04-27T08:21:32.000Z'), value: '100' }, + { date: new Date('2017-04-30T23:06:24.000Z'), value: '100' } ] }, { metric: 'uncovered_lines', history: [ - { date: new Date('2017-04-27T08:21:32+0200'), value: '12' }, - { date: new Date('2017-04-30T23:06:24+0200'), value: '50' } + { date: new Date('2017-04-27T08:21:32.000Z'), value: '12' }, + { date: new Date('2017-04-30T23:06:24.000Z'), value: '50' } ] } ]; @@ -83,7 +84,7 @@ const METRICS = [ const QUERY = { category: '', - from: new Date('2017-04-27T08:21:32+0200'), + from: new Date('2017-04-27T08:21:32.000Z'), graph: utils.DEFAULT_GRAPH, project: 'foo', to: undefined, @@ -91,16 +92,6 @@ const QUERY = { customMetrics: ['foo', 'bar', 'baz'] }; -jest.mock('moment', () => date => ({ - startOf: () => { - return { - valueOf: () => `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}` - }; - }, - toDate: () => new Date(date), - format: format => `Formated.${format}:${date.valueOf()}` -})); - describe('generateCoveredLinesMetric', () => { it('should correctly generate covered lines metric', () => { expect(utils.generateCoveredLinesMetric(HISTORY[1], HISTORY)).toMatchSnapshot(); @@ -116,6 +107,12 @@ describe('generateSeries', () => { }); describe('getAnalysesByVersionByDay', () => { + dates.startOfDay = jest.fn(date => { + const startDay = new Date(date); + startDay.setUTCHours(0, 0, 0, 0); + return startDay; + }); + it('should correctly map analysis by versions and by days', () => { expect( utils.getAnalysesByVersionByDay(ANALYSES, { @@ -141,8 +138,8 @@ describe('getAnalysesByVersionByDay', () => { customMetrics: [], graph: utils.DEFAULT_GRAPH, project: 'foo', - to: new Date('2017-06-09T11:12:27+0200'), - from: new Date('2017-05-18T14:13:07+0200') + to: new Date('2017-06-09T11:12:27.000Z'), + from: new Date('2017-05-18T14:13:07.000Z') }) ).toMatchSnapshot(); }); @@ -150,10 +147,10 @@ describe('getAnalysesByVersionByDay', () => { expect( utils.getAnalysesByVersionByDay( [ - { key: 'AVyMjlK1HjR_PLDzRbB9', date: new Date('2017-06-09T13:06:10+0200'), events: [] }, - { key: 'AVyM9n3cHjR_PLDzRciT', date: new Date('2017-06-09T11:12:27+0200'), events: [] }, - { key: 'AVyMjlK1HjR_PLDzRbB9', date: new Date('2017-06-09T11:12:27+0200'), events: [] }, - { key: 'AVxZtCpH7841nF4RNEMI', date: new Date('2017-05-18T14:13:07+0200'), events: [] } + { key: 'AVyMjlK1HjR_PLDzRbB9', date: new Date('2017-06-09T13:06:10.000Z'), events: [] }, + { key: 'AVyM9n3cHjR_PLDzRciT', date: new Date('2017-06-09T11:12:27.000Z'), events: [] }, + { key: 'AVyMjlK1HjR_PLDzRbB9', date: new Date('2017-06-09T11:12:27.000Z'), events: [] }, + { key: 'AVxZtCpH7841nF4RNEMI', date: new Date('2017-05-18T14:13:07.000Z'), events: [] } ], { category: '', @@ -208,7 +205,7 @@ describe('parseQuery', () => { it('should parse query with default values', () => { expect( utils.parseQuery({ - from: '2017-04-27T08:21:32+0200', + from: '2017-04-27T08:21:32.000Z', id: 'foo', custom_metrics: 'foo,bar,baz' }) @@ -219,11 +216,11 @@ describe('parseQuery', () => { describe('serializeQuery', () => { it('should serialize query for api request', () => { expect(utils.serializeQuery(QUERY)).toEqual({ - from: 'Formated.YYYY-MM-DDTHH:mm:ssZZ:1493274092000', + from: '2017-04-27T08:21:32+0000', project: 'foo' }); expect(utils.serializeQuery({ ...QUERY, graph: 'coverage', category: 'test' })).toEqual({ - from: 'Formated.YYYY-MM-DDTHH:mm:ssZZ:1493274092000', + from: '2017-04-27T08:21:32+0000', project: 'foo', category: 'test' }); @@ -233,14 +230,14 @@ describe('serializeQuery', () => { describe('serializeUrlQuery', () => { it('should serialize query for url', () => { expect(utils.serializeUrlQuery(QUERY)).toEqual({ - from: 'Formated.YYYY-MM-DDTHH:mm:ssZZ:1493274092000', + from: '2017-04-27T08:21:32+0000', id: 'foo', custom_metrics: 'foo,bar,baz' }); expect( utils.serializeUrlQuery({ ...QUERY, graph: 'coverage', category: 'test', customMetrics: [] }) ).toEqual({ - from: 'Formated.YYYY-MM-DDTHH:mm:ssZZ:1493274092000', + from: '2017-04-27T08:21:32+0000', id: 'foo', graph: 'coverage', category: 'test' @@ -256,8 +253,8 @@ describe('hasHistoryData', () => { name: 'foo', type: 'INT', data: [ - { x: new Date('2017-04-27T08:21:32+0200'), y: 2 }, - { x: new Date('2017-04-30T23:06:24+0200'), y: 2 } + { x: new Date('2017-04-27T08:21:32.000Z'), y: 2 }, + { x: new Date('2017-04-30T23:06:24.000Z'), y: 2 } ] } ]) @@ -273,8 +270,8 @@ describe('hasHistoryData', () => { name: 'bar', type: 'INT', data: [ - { x: new Date('2017-04-27T08:21:32+0200'), y: 2 }, - { x: new Date('2017-04-30T23:06:24+0200'), y: 2 } + { x: new Date('2017-04-27T08:21:32.000Z'), y: 2 }, + { x: new Date('2017-04-30T23:06:24.000Z'), y: 2 } ] } ]) @@ -284,7 +281,7 @@ describe('hasHistoryData', () => { { name: 'bar', type: 'INT', - data: [{ x: new Date('2017-04-27T08:21:32+0200'), y: 2 }] + data: [{ x: new Date('2017-04-27T08:21:32.000Z'), y: 2 }] } ]) ).toBeFalsy(); diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js index 4e5be7ea5db..044b1462323 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; -import moment from 'moment'; import { isEqual, sortBy } from 'lodash'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; import GraphHistory from './GraphHistory'; @@ -87,7 +86,7 @@ export default class GraphsHistory extends React.PureComponent { return acc.concat({ className: event.category, name: event.name, - date: moment(analysis.date).toDate() + date: new Date(analysis.date) }); }, []); return sortBy(filteredEvents, 'date'); diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.js index 4eb0e719a28..320f934e028 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.js @@ -20,7 +20,7 @@ // @flow import React from 'react'; import BubblePopup from '../../../components/common/BubblePopup'; -import FormattedDate from '../../../components/ui/FormattedDate'; +import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import GraphsTooltipsContent from './GraphsTooltipsContent'; import GraphsTooltipsContentEvents from './GraphsTooltipsContentEvents'; import GraphsTooltipsContentCoverage from './GraphsTooltipsContentCoverage'; @@ -98,7 +98,7 @@ export default class GraphsTooltips extends React.PureComponent { <BubblePopup customClass={customClass} position={{ top, left, width: TOOLTIP_WIDTH }}> <div className="project-activity-graph-tooltip"> <div className="project-activity-graph-tooltip-title spacer-bottom"> - <FormattedDate date={this.props.selectedDate} format="LL" /> + <DateTimeFormatter date={this.props.selectedDate} /> </div> <table className="width-100"> <tbody> diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js index 7f69288c33d..7b94b01483c 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js @@ -20,10 +20,9 @@ // @flow import React from 'react'; import classNames from 'classnames'; -import moment from 'moment'; import { throttle } from 'lodash'; import ProjectActivityAnalysis from './ProjectActivityAnalysis'; -import FormattedDate from '../../../components/ui/FormattedDate'; +import DateFormatter from '../../../components/intl/DateFormatter'; import { translate } from '../../../helpers/l10n'; import { activityQueryChanged, @@ -191,12 +190,9 @@ export default class ProjectActivityAnalysesList extends React.PureComponent { </div>} <ul className="project-activity-days-list"> {days.map(day => - <li - key={day} - className="project-activity-day" - data-day={moment(Number(day)).format('YYYY-MM-DD')}> + <li key={day} className="project-activity-day"> <div className="project-activity-date"> - <FormattedDate date={Number(day)} format="LL" /> + <DateFormatter date={Number(day)} long={true} /> </div> <ul className="project-activity-analyses-list"> {version.byDay[day] != null && diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js index f52a59b5943..bc17b6a46b6 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js @@ -23,7 +23,7 @@ import classNames from 'classnames'; import Events from './Events'; import AddEventForm from './forms/AddEventForm'; import RemoveAnalysisForm from './forms/RemoveAnalysisForm'; -import FormattedDate from '../../../components/ui/FormattedDate'; +import TimeTooltipFormatter from '../../../components/intl/TimeTooltipFormatter'; import { translate } from '../../../helpers/l10n'; /*:: import type { Analysis } from '../types'; */ @@ -65,7 +65,7 @@ export default class ProjectActivityAnalysis extends React.PureComponent { role="listitem" tabIndex="0"> <div className="project-activity-time spacer-right"> - <FormattedDate className="text-middle" date={date} format="LT" tooltipFormat="LTS" /> + <TimeTooltipFormatter className="text-middle" date={date} placement="right" /> </div> <div className="project-activity-analysis-icon big-spacer-right" title={analysisTitle} /> diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js index cdc41b48e99..2de698d4b52 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js @@ -20,7 +20,6 @@ // @flow import React from 'react'; import Helmet from 'react-helmet'; -import moment from 'moment'; import ProjectActivityPageHeader from './ProjectActivityPageHeader'; import ProjectActivityAnalysesList from './ProjectActivityAnalysesList'; import ProjectActivityGraphs from './ProjectActivityGraphs'; @@ -89,7 +88,7 @@ export default function ProjectActivityApp(props /*: Props */) { <div className="project-activity-layout-page-main"> <ProjectActivityGraphs analyses={analyses} - leakPeriodDate={moment(props.project.leakPeriodDate).toDate()} + leakPeriodDate={new Date(props.project.leakPeriodDate)} loading={props.graphLoading} measuresHistory={measuresHistory} metrics={props.metrics} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js index 61e9bd077c2..172e0d35bcf 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js @@ -19,7 +19,6 @@ */ // @flow import React from 'react'; -import moment from 'moment'; import { connect } from 'react-redux'; import { withRouter } from 'react-router'; import ProjectActivityApp from './ProjectActivityApp'; @@ -173,7 +172,7 @@ class ProjectActivityAppContainer extends React.PureComponent { return api .getProjectActivity({ ...parameters, ...additional }) .then(({ analyses, paging }) => ({ - analyses: analyses.map(analysis => ({ ...analysis, date: moment(analysis.date).toDate() })), + analyses: analyses.map(analysis => ({ ...analysis, date: new Date(analysis.date) })), paging })); }; @@ -187,7 +186,7 @@ class ProjectActivityAppContainer extends React.PureComponent { measures.map(measure => ({ metric: measure.metric, history: measure.history.map(analysis => ({ - date: moment(analysis.date).toDate(), + date: new Date(analysis.date), value: analysis.value })) })), diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.js index 1ece1355970..f6f85c29c9b 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityDateInput.js @@ -19,8 +19,9 @@ */ // @flow import React from 'react'; -import moment from 'moment'; +import { intlShape } from 'react-intl'; import DateInput from '../../../components/controls/DateInput'; +import { formatterOption } from '../../../components/intl/DateFormatter'; import { parseAsDate } from '../../../helpers/query'; import { translate } from '../../../helpers/l10n'; /*:: import type { RawQuery } from '../../../helpers/query'; */ @@ -36,13 +37,18 @@ type Props = { export default class ProjectActivityDateInput extends React.PureComponent { /*:: props: Props; */ + static contextTypes = { + intl: intlShape + }; + handleFromDateChange = (from /*: string */) => this.props.onChange({ from: parseAsDate(from) }); handleToDateChange = (to /*: string */) => this.props.onChange({ to: parseAsDate(to) }); handleResetClick = () => this.props.onChange({ from: null, to: null }); - formatDate = (date /*: ?Date */) => (date ? moment(date).format('YYYY-MM-DD') : null); + formatDate = (date /*: ?Date */) => + date ? this.context.intl.formatDate(date, formatterOption) : undefined; render() { return ( diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.js index 30864a995f9..6a0c61741f2 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.js @@ -20,6 +20,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import ProjectActivityAnalysesList from '../ProjectActivityAnalysesList'; +import * as dates from '../../../../helpers/dates'; import { DEFAULT_GRAPH } from '../../utils'; const ANALYSES = [ @@ -83,18 +84,14 @@ const DEFAULT_PROPS = { updateQuery: () => {} }; -jest.mock('moment', () => date => ({ - startOf: () => { - return { - valueOf: () => `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}` - }; - }, - toDate: () => new Date(date), - format: format => `Formated.${format}:${date}` -})); - window.Number = val => val; +dates.startOfDay = jest.fn(date => { + const startDay = new Date(date); + startDay.setUTCHours(0, 0, 0, 0); + return startDay; +}); + it('should render correctly', () => { expect(shallow(<ProjectActivityAnalysesList {...DEFAULT_PROPS} />)).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityDateInput-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityDateInput-test.js index fa199664561..6b93eaae296 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityDateInput-test.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityDateInput-test.js @@ -18,12 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallowWithIntl } from '../../../../helpers/testUtils'; import ProjectActivityDateInput from '../ProjectActivityDateInput'; it('should render correctly the date inputs', () => { expect( - shallow( + shallowWithIntl( <ProjectActivityDateInput from={new Date('2016-10-27T12:21:15+0000')} to={new Date('2016-12-27T12:21:15+0000')} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltips-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltips-test.js.snap index aea593d413e..e2ab4b039a8 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltips-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltips-test.js.snap @@ -17,9 +17,8 @@ exports[`should not add separators if not needed 1`] = ` <div className="project-activity-graph-tooltip-title spacer-bottom" > - <FormattedDate + <DateTimeFormatter date={2011-10-01T22:01:00.000Z} - format="LL" /> </div> <table @@ -53,9 +52,8 @@ exports[`should render correctly for issues graphs 1`] = ` <div className="project-activity-graph-tooltip-title spacer-bottom" > - <FormattedDate + <DateTimeFormatter date={2011-10-01T22:01:00.000Z} - format="LL" /> </div> <table @@ -109,9 +107,8 @@ exports[`should render correctly for random graphs 1`] = ` <div className="project-activity-graph-tooltip-title spacer-bottom" > - <FormattedDate + <DateTimeFormatter date={2011-10-25T10:27:41.000Z} - format="LL" /> </div> <table diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityAnalysesList-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityAnalysesList-test.js.snap index 32622085444..00938d611a8 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityAnalysesList-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityAnalysesList-test.js.snap @@ -25,14 +25,13 @@ exports[`should correctly filter analyses by category 1`] = ` > <li className="project-activity-day" - data-day="Formated.YYYY-MM-DD:2016-9-24" > <div className="project-activity-date" > - <FormattedDate - date="2016-9-24" - format="LL" + <DateFormatter + date="1477267200000" + long={true} /> </div> <ul @@ -95,14 +94,13 @@ exports[`should correctly filter analyses by date range 1`] = ` > <li className="project-activity-day" - data-day="Formated.YYYY-MM-DD:2016-9-27" > <div className="project-activity-date" > - <FormattedDate - date="2016-9-27" - format="LL" + <DateFormatter + date="1477526400000" + long={true} /> </div> <ul @@ -165,14 +163,13 @@ exports[`should render correctly 1`] = ` > <li className="project-activity-day" - data-day="Formated.YYYY-MM-DD:2016-9-27" > <div className="project-activity-date" > - <FormattedDate - date="2016-9-27" - format="LL" + <DateFormatter + date="1477526400000" + long={true} /> </div> <ul @@ -241,14 +238,13 @@ exports[`should render correctly 1`] = ` > <li className="project-activity-day" - data-day="Formated.YYYY-MM-DD:2016-9-26" > <div className="project-activity-date" > - <FormattedDate - date="2016-9-26" - format="LL" + <DateFormatter + date="1477440000000" + long={true} /> </div> <ul @@ -288,14 +284,13 @@ exports[`should render correctly 1`] = ` </li> <li className="project-activity-day" - data-day="Formated.YYYY-MM-DD:2016-9-24" > <div className="project-activity-date" > - <FormattedDate - date="2016-9-24" - format="LL" + <DateFormatter + date="1477267200000" + long={true} /> </div> <ul diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.js.snap index bc15b05c60e..95d05668fef 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityDateInput-test.js.snap @@ -8,7 +8,7 @@ exports[`should render correctly the date inputs 1`] = ` name="from" onChange={[Function]} placeholder="from" - value="2016-10-27" + value="10/27/2016" /> — <DateInput @@ -17,7 +17,7 @@ exports[`should render correctly the date inputs 1`] = ` name="to" onChange={[Function]} placeholder="to" - value="2016-12-27" + value="12/27/2016" /> <button className="spacer-left" diff --git a/server/sonar-web/src/main/js/apps/projectActivity/utils.js b/server/sonar-web/src/main/js/apps/projectActivity/utils.js index 12694dba2bf..03eb0510b61 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/utils.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/utils.js @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // @flow -import moment from 'moment'; import { chunk, flatMap, groupBy, isEqual, sortBy } from 'lodash'; import { cleanQuery, @@ -29,6 +28,7 @@ import { serializeDate, serializeString } from '../../helpers/query'; +import { startOfDay } from '../../helpers/dates'; import { getLocalizedMetricName, translate } from '../../helpers/l10n'; /*:: import type { Analysis, MeasureHistory, Metric, Query } from './types'; */ /*:: import type { RawQuery } from '../../helpers/query'; */ @@ -157,7 +157,7 @@ export function getAnalysesByVersionByDay(analyses /*: Array<Analysis> */, query acc.push(currentVersion); } - const day = moment(analysis.date).startOf('day').valueOf().toString(); + const day = startOfDay(new Date(analysis.date)).getTime().toString(); let matchFilters = true; if (query.category || query.from || query.to) { diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.js index b6af4d5cd10..695136bb6c6 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.js @@ -20,8 +20,9 @@ // @flow import React from 'react'; import classNames from 'classnames'; -import moment from 'moment'; import { Link } from 'react-router'; +import { FormattedRelative } from 'react-intl'; +import DateTimeFormatter from '../../../components/intl/DateTimeFormatter.tsx'; import ProjectCardQualityGate from './ProjectCardQualityGate'; import ProjectCardLeakMeasures from './ProjectCardLeakMeasures'; import FavoriteContainer from '../../../components/controls/FavoriteContainer'; @@ -90,19 +91,19 @@ export default function ProjectCardLeak({ measures, organization, project } /*: hasLeakPeriodStart && <div className="project-card-dates note text-right pull-right"> {hasLeakPeriodStart && - <span className="project-card-leak-date pull-right"> - {translateWithParameters( - 'projects.leak_period_x', - moment(project.leakPeriodDate).fromNow() - )} - </span>} + <FormattedRelative value={project.leakPeriodDate}> + {fromNow => + <span className="project-card-leak-date pull-right"> + {translateWithParameters('projects.leak_period_x', fromNow)} + </span>} + </FormattedRelative>} {isProjectAnalyzed && - <span> - {translateWithParameters( - 'projects.last_analysis_on_x', - moment(project.analysisDate).format('LLL') - )} - </span>} + <DateTimeFormatter date={project.analysisDate}> + {formattedDate => + <span> + {translateWithParameters('projects.last_analysis_on_x', formattedDate)} + </span>} + </DateTimeFormatter>} </div>} </div> diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.js index 5d8cddae025..baea9ad442e 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.js @@ -20,8 +20,8 @@ // @flow import React from 'react'; import classNames from 'classnames'; -import moment from 'moment'; import { Link } from 'react-router'; +import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import ProjectCardQualityGate from './ProjectCardQualityGate'; import ProjectCardOverallMeasures from './ProjectCardOverallMeasures'; import FavoriteContainer from '../../../components/controls/FavoriteContainer'; @@ -87,12 +87,12 @@ export default function ProjectCardOverall({ measures, organization, project } / </div> {isProjectAnalyzed && <div className="project-card-dates note text-right"> - <span className="big-spacer-left"> - {translateWithParameters( - 'projects.last_analysis_on_x', - moment(project.analysisDate).format('LLL') - )} - </span> + <DateTimeFormatter date={project.analysisDate}> + {formattedDate => + <span className="big-spacer-left"> + {translateWithParameters('projects.last_analysis_on_x', formattedDate)} + </span>} + </DateTimeFormatter> </div>} </div> diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.js index 46819f2bd13..2ac25e044ab 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.js +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.js @@ -35,15 +35,11 @@ const MEASURES = { new_bugs: 12 }; -jest.mock('moment', () => () => ({ - format: () => 'March 1, 2017 9:36 AM', - fromNow: () => 'a month ago' -})); - it('should display analysis date and leak start date', () => { const card = shallow(<ProjectCardLeak type="leak" measures={MEASURES} project={PROJECT} />); expect(card.find('.project-card-dates').exists()).toBeTruthy(); - expect(card.find('.project-card-dates').find('span').getNodes()).toHaveLength(2); + expect(card.find('.project-card-dates').find('FormattedRelative').getNodes()).toHaveLength(1); + expect(card.find('.project-card-dates').find('DateTimeFormatter').getNodes()).toHaveLength(1); }); it('should not display analysis date or leak start date', () => { diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.js index b6f5698f231..91144662ec5 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.js +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.js @@ -35,11 +35,6 @@ const MEASURES = { new_bugs: 12 }; -jest.mock('moment', () => () => ({ - format: () => 'March 1, 2017 9:36 AM', - fromNow: () => 'a month ago' -})); - it('should display analysis date (and not leak period) when defined', () => { expect( shallow(<ProjectCardOverall measures={{}} project={PROJECT} />) diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.js.snap index cb539fbafe6..591ed49e1c8 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.js.snap @@ -35,14 +35,13 @@ exports[`should display the leak measures and quality gate 1`] = ` <div className="project-card-dates note text-right pull-right" > - <span - className="project-card-leak-date pull-right" - > - projects.leak_period_x.a month ago - </span> - <span> - projects.last_analysis_on_x.March 1, 2017 9:36 AM - </span> + <FormattedRelative + updateInterval={10000} + value="2016-12-01" + /> + <DateTimeFormatter + date="2017-01-01" + /> </div> </div> <div diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.js.snap index b93bf252c31..720f0f20129 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.js.snap @@ -35,11 +35,9 @@ exports[`should display the overall measures and quality gate 1`] = ` <div className="project-card-dates note text-right" > - <span - className="big-spacer-left" - > - projects.last_analysis_on_x.March 1, 2017 9:36 AM - </span> + <DateTimeFormatter + date="2017-01-01" + /> </div> </div> <div diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx index 1dbfdda95fd..1e94f724259 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx @@ -19,10 +19,11 @@ */ import * as React from 'react'; import { Link } from 'react-router'; -import * as moment from 'moment'; import ChangesList from './ChangesList'; +import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import { translate } from '../../../helpers/l10n'; import { getRulesUrl } from '../../../helpers/urls'; +import { differenceInSeconds } from '../../../helpers/dates'; import { ProfileChangelogEvent } from '../types'; interface Props { @@ -35,7 +36,8 @@ export default function Changelog(props: Props) { const rows = props.events.map((event, index) => { const prev = index > 0 ? props.events[index - 1] : null; - const isSameDate = prev != null && moment(prev.date).diff(event.date, 'seconds') < 10; + const isSameDate = + prev != null && differenceInSeconds(new Date(prev.date), new Date(event.date)) < 10; const isBulkChange = prev != null && isSameDate && @@ -51,7 +53,7 @@ export default function Changelog(props: Props) { return ( <tr key={index} className={className}> <td className="thin nowrap"> - {!isBulkChange && moment(event.date).format('LLL')} + {!isBulkChange && <DateTimeFormatter date={event.date} />} </td> <td className="thin nowrap"> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx index b28f8e236e0..6272bcc2f12 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx @@ -45,7 +45,7 @@ it('should render events', () => { it('should render event date', () => { const events = [createEvent()]; const changelog = shallow(<Changelog events={events} organization={null} />); - expect(changelog.text()).toContain('2016'); + expect(changelog.find('DateTimeFormatter')).toHaveLength(1); }); it('should render author', () => { diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.tsx index 947b15e419c..3841c70ee0f 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.tsx @@ -18,7 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as moment from 'moment'; +import { FormattedRelative } from 'react-intl'; +import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; +import Tooltip from '../../../components/controls/Tooltip'; import { translate } from '../../../helpers/l10n'; interface Props { @@ -27,9 +29,11 @@ interface Props { export default function ProfileDate({ date }: Props) { return date - ? <span title={moment(date).format('LLL')} data-toggle="tooltip"> - {moment(date).fromNow()} - </span> + ? <Tooltip overlay={<DateTimeFormatter date={date} />}> + <span> + <FormattedRelative value={date} /> + </span> + </Tooltip> : <span> {translate('never')} </span>; diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx index 84df54ceb49..2119c23ae3e 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx @@ -19,17 +19,15 @@ */ import * as React from 'react'; import { Link } from 'react-router'; -import * as moment from 'moment'; import { sortBy } from 'lodash'; import { searchRules } from '../../../api/rules'; import { translateWithParameters, translate } from '../../../helpers/l10n'; import { getRulesUrl } from '../../../helpers/urls'; +import { toShortNotSoISOString } from '../../../helpers/dates'; import { formatMeasure } from '../../../helpers/measures'; const RULES_LIMIT = 10; -const PERIOD_START_MOMENT = moment().subtract(1, 'year'); - function parseRules(r: any) { const { rules, actives } = r; return rules.map((rule: any) => { @@ -55,8 +53,16 @@ interface State { } export default class EvolutionRules extends React.PureComponent<Props, State> { + periodStartDate: string; mounted: boolean; - state: State = {}; + + constructor(props: Props) { + super(props); + this.state = {}; + const startDate = new Date(); + startDate.setFullYear(startDate.getFullYear() - 1); + this.periodStartDate = toShortNotSoISOString(startDate); + } componentDidMount() { this.mounted = true; @@ -69,7 +75,7 @@ export default class EvolutionRules extends React.PureComponent<Props, State> { loadLatestRules() { const data = { - available_since: PERIOD_START_MOMENT.format('YYYY-MM-DD'), + available_since: this.periodStartDate, s: 'createdAt', asc: false, ps: RULES_LIMIT, @@ -92,9 +98,7 @@ export default class EvolutionRules extends React.PureComponent<Props, State> { } const newRulesUrl = getRulesUrl( - { - available_since: PERIOD_START_MOMENT.format('YYYY-MM-DD') - }, + { available_since: this.periodStartDate }, this.props.organization ); diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx index 578020e7129..5252d1c1840 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx @@ -18,9 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as moment from 'moment'; +import DateFormatter from '../../../components/intl/DateFormatter'; import ProfileLink from '../components/ProfileLink'; -import { translate } from '../../../helpers/l10n'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; import { isStagnant } from '../utils'; import { Profile } from '../types'; @@ -29,7 +29,7 @@ interface Props { profiles: Profile[]; } -export default function EvolutionStagnan(props: Props) { +export default function EvolutionStagnant(props: Props) { // TODO filter built-in out const outdated = props.profiles.filter(isStagnant); @@ -60,11 +60,16 @@ export default function EvolutionStagnan(props: Props) { {profile.name} </ProfileLink> </div> - <div className="note"> - {profile.languageName} - {', '} - updated on {moment(profile.rulesUpdatedAt).format('LL')} - </div> + <DateFormatter date={profile.rulesUpdatedAt} long={true}> + {formattedDate => + <div className="note"> + {translateWithParameters( + 'quality_profiles.x_updated_on_y', + profile.languageName, + formattedDate + )} + </div>} + </DateFormatter> </li> )} </ul> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/utils.ts b/server/sonar-web/src/main/js/apps/quality-profiles/utils.ts index cc7140c0fd4..d058aa01acd 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/utils.ts +++ b/server/sonar-web/src/main/js/apps/quality-profiles/utils.ts @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { sortBy } from 'lodash'; -import * as moment from 'moment'; +import { differenceInYears, isValidDate } from '../../helpers/dates'; import { Profile } from './types'; export function sortProfiles(profiles: Profile[]) { @@ -65,8 +65,14 @@ export function createFakeProfile(overrides?: any) { }; } -export function isStagnant(profile: Profile) { - return moment().diff(moment(profile.userUpdatedAt), 'years') >= 1; +export function isStagnant(profile: Profile): boolean { + if (profile.userUpdatedAt) { + const updateDate = new Date(profile.userUpdatedAt); + if (isValidDate(updateDate)) { + return differenceInYears(new Date(), updateDate) >= 1; + } + } + return false; } export const getProfilesPath = (organization: string | null | undefined) => diff --git a/server/sonar-web/src/main/js/apps/settings/licenses/LicenseRow.js b/server/sonar-web/src/main/js/apps/settings/licenses/LicenseRow.js index f8ef6d4c7f0..d6b26fd3a75 100644 --- a/server/sonar-web/src/main/js/apps/settings/licenses/LicenseRow.js +++ b/server/sonar-web/src/main/js/apps/settings/licenses/LicenseRow.js @@ -19,7 +19,7 @@ */ import React from 'react'; import PropTypes from 'prop-types'; -import moment from 'moment'; +import DateFormatter from '../../../components/intl/DateFormatter'; import LicenseStatus from './LicenseStatus'; import LicenseChangeForm from './LicenseChangeForm'; @@ -50,7 +50,7 @@ export default class LicenseRow extends React.PureComponent { <td className="js-expiration text-middle"> {license.expiration != null && <div className={license.invalidExpiration ? 'text-danger' : null}> - {moment(license.expiration).format('LL')} + <DateFormatter date={license.expiration} long={true} /> </div>} </td> <td className="js-type text-middle"> diff --git a/server/sonar-web/src/main/js/apps/settings/licenses/__tests__/LicenseRow-test.js b/server/sonar-web/src/main/js/apps/settings/licenses/__tests__/LicenseRow-test.js index 4d7c758d416..94e3d55b633 100644 --- a/server/sonar-web/src/main/js/apps/settings/licenses/__tests__/LicenseRow-test.js +++ b/server/sonar-web/src/main/js/apps/settings/licenses/__tests__/LicenseRow-test.js @@ -73,7 +73,7 @@ it('should render expiration', () => { '.js-expiration' ); expect(licenseExpiration.length).toBe(1); - expect(licenseExpiration.text()).toContain('2015'); + expect(licenseExpiration.find('DateFormatter')).toHaveLength(1); }); it('should render invalid expiration', () => { |