]> source.dussan.org Git - sonarqube.git/commitdiff
refactor measure formatting
authorStas Vilchik <vilchiks@gmail.com>
Mon, 2 Nov 2015 10:23:36 +0000 (11:23 +0100)
committerStas Vilchik <vilchiks@gmail.com>
Mon, 2 Nov 2015 13:02:10 +0000 (14:02 +0100)
27 files changed:
server/sonar-web/package.json
server/sonar-web/src/main/js/apps/issues/facets/creation-date-facet.js
server/sonar-web/src/main/js/apps/issues/helpers/format-facet-value.js
server/sonar-web/src/main/js/apps/overview/coverage/coverage-details.js
server/sonar-web/src/main/js/apps/overview/domain/bubble-chart.js
server/sonar-web/src/main/js/apps/overview/domain/measures-list.js
server/sonar-web/src/main/js/apps/overview/domain/timeline.js
server/sonar-web/src/main/js/apps/overview/domain/treemap.js
server/sonar-web/src/main/js/apps/overview/general/coverage.js
server/sonar-web/src/main/js/apps/overview/general/duplications.js
server/sonar-web/src/main/js/apps/overview/general/issues.js
server/sonar-web/src/main/js/apps/overview/general/size.js
server/sonar-web/src/main/js/apps/overview/helpers/measure-variation.js
server/sonar-web/src/main/js/apps/overview/helpers/measure.js
server/sonar-web/src/main/js/apps/overview/helpers/rating.js
server/sonar-web/src/main/js/apps/overview/issues/assignees.js
server/sonar-web/src/main/js/apps/overview/issues/severities.js
server/sonar-web/src/main/js/apps/overview/issues/tags.js
server/sonar-web/src/main/js/apps/overview/size/complexity-distribution.js
server/sonar-web/src/main/js/apps/overview/size/language-distribution.js
server/sonar-web/src/main/js/apps/quality-profiles/profile-view.js
server/sonar-web/src/main/js/helpers/handlebars-helpers.js
server/sonar-web/src/main/js/helpers/measures.js [new file with mode: 0644]
server/sonar-web/src/main/js/libs/application.js
server/sonar-web/src/main/js/widgets/issue-filter/widget.js
server/sonar-web/tests/application-test.js
server/sonar-web/tests/helpers/measures-test.js [new file with mode: 0644]

index 9a3c68700906d108dcf74f0d5507e9e142c37b78..e73b954a6ac67888c94b61de06b19f7b1287d073 100644 (file)
@@ -37,6 +37,7 @@
     "jsdom": "6.5.1",
     "mocha": "2.3.3",
     "moment": "2.10.6",
+    "numeral": "^1.5.3",
     "react": "^0.14.0",
     "react-addons-test-utils": "^0.14.0",
     "react-dom": "^0.14.0",
@@ -58,7 +59,8 @@
   "browserify-shim": {
     "jquery": "global:jQuery",
     "underscore": "global:_",
-    "d3": "global:d3"
+    "d3": "global:d3",
+    "numeral": "global:numeral"
   },
   "browserify": {
     "transform": [
index ed5de16a26ebe51019bf0273198d764d2ab5c192..96dfc2500a71939eb461b54f7710b7267cb7bc2d 100644 (file)
@@ -4,6 +4,7 @@ import moment from 'moment';
 import BaseFacet from './base-facet';
 import Template from '../templates/facets/issues-creation-date-facet.hbs';
 import '../../../components/widgets/barchart.js';
+import { formatMeasure } from '../../../helpers/measures';
 
 export default BaseFacet.extend({
   template: Template,
@@ -49,7 +50,7 @@ export default BaseFacet.extend({
     }
     values = values.map(function (v) {
       var format = that.options.app.state.getFacetMode() === 'count' ? 'SHORT_INT' : 'SHORT_WORK_DUR';
-      var text = window.formatMeasure(v.count, format);
+      var text = formatMeasure(v.count, format);
       return _.extend(v, { text: text });
     });
     return this.$('.js-barchart').barchart(values);
index 94ddec317a42e7910a7bff50d99ddaa6edf57a36..5d9ba4250ae367ebf0e8e483f4404b79400a3619 100644 (file)
@@ -1,8 +1,9 @@
 import Handlebars from 'hbsfy/runtime';
+import { formatMeasure } from '../../../helpers/measures';
 
 Handlebars.registerHelper('formatFacetValue', function (value, facetMode) {
   var formatter = facetMode === 'debt' ? 'SHORT_WORK_DUR' : 'SHORT_INT';
-  return window.formatMeasure(value, formatter);
+  return formatMeasure(value, formatter);
 });
 
 
index 64904641c1500bd8652db5f82c47497914f05534..3b0057038d0f955a2556f10e0132e8093d6daff9 100644 (file)
@@ -2,6 +2,7 @@ import React from 'react';
 
 import { getMeasures } from '../../../api/measures';
 import DrilldownLink from '../helpers/drilldown-link';
+import { formatMeasure } from '../../../helpers/measures';
 
 
 const METRICS = [
@@ -36,7 +37,7 @@ export class CoverageDetails extends React.Component {
   renderValue (value, metricKey) {
     if (value != null) {
       return <DrilldownLink component={this.props.component.key} metric={metricKey}>
-        {window.formatMeasure(value, 'PERCENT')}
+        {formatMeasure(value, 'PERCENT')}
       </DrilldownLink>;
     } else {
       return '—';
index 6adef652fe69c9b86408e1b860f95676e7c285ec..4a72a92dffc529e0c83e1fcfee555bc420755497 100644 (file)
@@ -3,6 +3,7 @@ import React from 'react';
 import { BubbleChart } from '../../../components/charts/bubble-chart';
 import { getProjectUrl } from '../../../helpers/Url';
 import { getFiles } from '../../../api/components';
+import { formatMeasure } from '../../../helpers/measures';
 
 
 const HEIGHT = 360;
@@ -63,9 +64,9 @@ export class DomainBubbleChart extends React.Component {
 
     let inner = [
       component.name,
-      `${this.state.xMetric.name}: ${window.formatMeasure(getMeasure(component, this.props.xMetric), this.state.xMetric.type)}`,
-      `${this.state.yMetric.name}: ${window.formatMeasure(getMeasure(component, this.props.yMetric), this.state.yMetric.type)}`,
-      `${sizeMetricsTitle}: ${window.formatMeasure(this.getSizeMetricsValue(component), sizeMetricsType)}`
+      `${this.state.xMetric.name}: ${formatMeasure(getMeasure(component, this.props.xMetric), this.state.xMetric.type)}`,
+      `${this.state.yMetric.name}: ${formatMeasure(getMeasure(component, this.props.yMetric), this.state.yMetric.type)}`,
+      `${sizeMetricsTitle}: ${formatMeasure(this.getSizeMetricsValue(component), sizeMetricsType)}`
     ].join('<br>');
     return `<div class="text-left">${inner}</div>`;
   }
@@ -90,8 +91,8 @@ export class DomainBubbleChart extends React.Component {
         tooltip: this.getTooltip(component)
       };
     });
-    let formatXTick = (tick) => window.formatMeasure(tick, this.state.xMetric.type);
-    let formatYTick = (tick) => window.formatMeasure(tick, this.state.yMetric.type);
+    let formatXTick = (tick) => formatMeasure(tick, this.state.xMetric.type);
+    let formatYTick = (tick) => formatMeasure(tick, this.state.yMetric.type);
     return <BubbleChart items={items}
                         height={HEIGHT}
                         padding={[25, 30, 50, 60]}
index 32c10b9e43e7ff5123a9436426374ada9c7a8b4e..d37d4b97dd99baf35a66aeef7f04d10e282df19b 100644 (file)
@@ -3,6 +3,7 @@ import React from 'react';
 
 import DrilldownLink from '../helpers/drilldown-link';
 import { getMeasures } from '../../../api/measures';
+import { formatMeasure } from '../../../helpers/measures';
 
 
 export class DomainMeasuresList extends React.Component {
@@ -28,7 +29,7 @@ export class DomainMeasuresList extends React.Component {
   renderValue (value, metricKey, metricType) {
     if (value != null) {
       return <DrilldownLink component={this.props.component.key} metric={metricKey}>
-        {window.formatMeasure(value, metricType)}
+        {formatMeasure(value, metricType)}
       </DrilldownLink>;
     } else {
       return '—';
index cef1012c0126b08dcce028597dea79fcf4f10cdd..88b95bcef3451a93b95a6d1cb4b6bbcdf7cf5eb6 100644 (file)
@@ -5,6 +5,7 @@ import React from 'react';
 import { LineChart } from '../../../components/charts/line-chart';
 import { getTimeMachineData } from '../../../api/time-machine';
 import { getEvents } from '../../../api/events';
+import { formatMeasure } from '../../../helpers/measures';
 
 
 const HEIGHT = 280;
@@ -112,7 +113,7 @@ export class DomainTimeline extends React.Component {
     let xTicks = events.map(event => event.version.substr(0, 6));
 
     let xValues = events.map(event => {
-      return currentMetricType === 'RATING' ? event.value : window.formatMeasure(event.value, currentMetricType);
+      return currentMetricType === 'RATING' ? event.value : formatMeasure(event.value, currentMetricType);
     });
 
     // TODO use leak period
index 1592b58a8e2809bad0803e041097b7d955625f38..2bc32e36b7ee7c68e456eddabe2e7ef91898c2ec 100644 (file)
@@ -3,6 +3,7 @@ import React from 'react';
 
 import { Treemap } from '../../../components/charts/treemap';
 import { getChildren } from '../../../api/components';
+import { formatMeasure } from '../../../helpers/measures';
 
 const HEIGHT = 360;
 
@@ -43,10 +44,10 @@ export class DomainTreemap extends React.Component {
   getTooltip (component) {
     let inner = [
       component.name,
-      `${this.state.sizeMetric.name}: ${window.formatMeasure(component.measures[this.props.sizeMetric], this.state.sizeMetric.type)}`
+      `${this.state.sizeMetric.name}: ${formatMeasure(component.measures[this.props.sizeMetric], this.state.sizeMetric.type)}`
     ];
     if (this.state.colorMetric) {
-      inner.push(`${this.state.colorMetric.name}: ${window.formatMeasure(component.measures[this.props.colorMetric], this.state.colorMetric.type)}`);
+      inner.push(`${this.state.colorMetric.name}: ${formatMeasure(component.measures[this.props.colorMetric], this.state.colorMetric.type)}`);
     }
     inner = inner.join('<br>');
     return `<div class="text-left">${inner}</div>`;
index 5637d4c227dc43ba2e882c3da81dc536bc5219a4..2af648184ac5f580eeb8f969a6281f6b0b2aa5e3 100644 (file)
@@ -4,6 +4,7 @@ import { Domain, DomainHeader, DomainPanel, DomainNutshell, DomainLeak, Measures
 import DrilldownLink from '../helpers/drilldown-link';
 import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
 import { getMetricName } from '../helpers/metrics';
+import { formatMeasure } from '../../../helpers/measures';
 
 
 export const GeneralCoverage = React.createClass({
@@ -18,7 +19,7 @@ export const GeneralCoverage = React.createClass({
   renderNewCoverage () {
     if (this.props.leak['new_overall_coverage'] != null) {
       return <DrilldownLink component={this.props.component.key} metric="new_overall_coverage" period="1">
-        {window.formatMeasure(this.props.leak['new_overall_coverage'], 'PERCENT')}
+        {formatMeasure(this.props.leak['new_overall_coverage'], 'PERCENT')}
       </DrilldownLink>;
     } else {
       return <span>—</span>;
@@ -52,12 +53,12 @@ export const GeneralCoverage = React.createClass({
           <MeasuresList>
             <Measure label={getMetricName('coverage')}>
               <DrilldownLink component={this.props.component.key} metric="overall_coverage">
-                {window.formatMeasure(this.props.measures['overall_coverage'], 'PERCENT')}
+                {formatMeasure(this.props.measures['overall_coverage'], 'PERCENT')}
               </DrilldownLink>
             </Measure>
             <Measure label={getMetricName('tests')}>
               <DrilldownLink component={this.props.component.key} metric="tests">
-                {window.formatMeasure(this.props.measures['tests'], 'SHORT_INT')}
+                {formatMeasure(this.props.measures['tests'], 'SHORT_INT')}
               </DrilldownLink>
             </Measure>
           </MeasuresList>
index d316eea8ba6e5035042cbae4485c2e47df6e4c43..bd95a2d4a3985f7ae156e48a37266d36ec08d5f4 100644 (file)
@@ -4,6 +4,7 @@ import { Domain, DomainHeader, DomainPanel, DomainNutshell, DomainLeak, Measures
 import DrilldownLink from '../helpers/drilldown-link';
 import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
 import { getMetricName } from '../helpers/metrics';
+import { formatMeasure, formatMeasureVariation } from '../../../helpers/measures';
 
 
 export const GeneralDuplications = React.createClass({
@@ -21,7 +22,7 @@ export const GeneralDuplications = React.createClass({
     return <DomainLeak>
       <MeasuresList>
         <Measure label={getMetricName('duplications')}>
-          {window.formatMeasureVariation(this.props.leak['duplicated_lines_density'], 'PERCENT')}
+          {formatMeasureVariation(this.props.leak['duplicated_lines_density'], 'PERCENT')}
         </Measure>
       </MeasuresList>
       {this.renderTimeline('after')}
@@ -38,12 +39,12 @@ export const GeneralDuplications = React.createClass({
           <MeasuresList>
             <Measure label={getMetricName('duplications')}>
               <DrilldownLink component={this.props.component.key} metric="duplicated_lines_density">
-                {window.formatMeasure(this.props.measures['duplicated_lines_density'], 'PERCENT')}
+                {formatMeasure(this.props.measures['duplicated_lines_density'], 'PERCENT')}
               </DrilldownLink>
             </Measure>
             <Measure label={getMetricName('duplicated_blocks')}>
               <DrilldownLink component={this.props.component.key} metric="duplicated_blocks">
-                {window.formatMeasure(this.props.measures['duplicated_blocks'], 'SHORT_INT')}
+                {formatMeasure(this.props.measures['duplicated_blocks'], 'SHORT_INT')}
               </DrilldownLink>
             </Measure>
           </MeasuresList>
index b97b0db57f94073ec82e171f351e8500cd7e20be..e34bb88b073fd4875cac8deedcdbf5af12fa334d 100644 (file)
@@ -11,6 +11,7 @@ import StatusIcon from '../../../components/shared/status-icon';
 import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
 import { getMetricName } from '../helpers/metrics';
 import { SEVERITIES } from '../../../helpers/constants';
+import { formatMeasure, formatMeasureVariation } from '../../../helpers/measures';
 
 
 export const GeneralIssues = React.createClass({
@@ -30,7 +31,7 @@ export const GeneralIssues = React.createClass({
         </td>
         <td className="thin nowrap text-right">
           <IssuesLink component={this.props.component.key} params={{ resolved: 'false', severities: s }}>
-            {window.formatMeasure(measure, 'SHORT_INT')}
+            {formatMeasure(measure, 'SHORT_INT')}
           </IssuesLink>
         </td>
       </tr>;
@@ -55,13 +56,13 @@ export const GeneralIssues = React.createClass({
         <Measure label={getMetricName('new_issues')}>
           <IssuesLink component={this.props.component.key}
                       params={{ resolved: 'false', createdAfter: createdAfter }}>
-            {window.formatMeasureVariation(this.props.leak.issues, 'SHORT_INT')}
+            {formatMeasureVariation(this.props.leak.issues, 'SHORT_INT')}
           </IssuesLink>
         </Measure>
         <Measure label={getMetricName('new_debt')}>
           <IssuesLink component={this.props.component.key}
                       params={{ resolved: 'false', createdAfter: createdAfter, facetMode: 'debt' }}>
-            {window.formatMeasureVariation(this.props.leak.debt, 'SHORT_WORK_DUR')}
+            {formatMeasureVariation(this.props.leak.debt, 'SHORT_WORK_DUR')}
           </IssuesLink>
         </Measure>
       </MeasuresList>
@@ -70,22 +71,21 @@ export const GeneralIssues = React.createClass({
           <span className="spacer-right"><SeverityIcon severity="BLOCKER"/></span>
           <IssuesLink component={this.props.component.key}
                       params={{ resolved: 'false', severities: 'BLOCKER', createdAfter: createdAfter }}>
-            {window.formatMeasureVariation(this.props.leak.issuesSeverities[0], 'SHORT_INT')}
+            {formatMeasureVariation(this.props.leak.issuesSeverities[0], 'SHORT_INT')}
           </IssuesLink>
         </Measure>
         <Measure label={getMetricName('new_critical_issues')}>
           <span className="spacer-right"><SeverityIcon severity="CRITICAL"/></span>
           <IssuesLink component={this.props.component.key}
                       params={{ resolved: 'false', severities: 'CRITICAL', createdAfter: createdAfter }}>
-            {window.formatMeasureVariation(this.props.leak.issuesSeverities[1], 'SHORT_INT')}
+            {formatMeasureVariation(this.props.leak.issuesSeverities[1], 'SHORT_INT')}
           </IssuesLink>
         </Measure>
         <Measure label={getMetricName('new_open_issues')}>
           <span className="spacer-right"><StatusIcon status="OPEN"/></span>
           <IssuesLink component={this.props.component.key}
                       params={{ resolved: 'false', statuses: 'OPEN,REOPENED', createdAfter: createdAfter }}>
-            {window.formatMeasureVariation(this.props.leak.issuesStatuses[0] + this.props.leak.issuesStatuses[1],
-                'SHORT_INT')}
+            {formatMeasureVariation(this.props.leak.issuesStatuses[0] + this.props.leak.issuesStatuses[1], 'SHORT_INT')}
           </IssuesLink>
         </Measure>
       </MeasuresList>
@@ -108,12 +108,12 @@ export const GeneralIssues = React.createClass({
             </Measure>
             <Measure label={getMetricName('issues')}>
               <IssuesLink component={this.props.component.key} params={{ resolved: 'false' }}>
-                {window.formatMeasure(this.props.measures.issues, 'SHORT_INT')}
+                {formatMeasure(this.props.measures.issues, 'SHORT_INT')}
               </IssuesLink>
             </Measure>
             <Measure label={getMetricName('debt')}>
               <IssuesLink component={this.props.component.key} params={{ resolved: 'false', facetMode: 'debt' }}>
-                {window.formatMeasure(this.props.measures.debt, 'SHORT_WORK_DUR')}
+                {formatMeasure(this.props.measures.debt, 'SHORT_WORK_DUR')}
               </IssuesLink>
             </Measure>
             <Measure composite={true}>
index ac43ab6b001852fca5de3eca40887d2f29d2e018..8642b9918aeb479067e35b8825f9c5f6bbc9a71c 100644 (file)
@@ -4,6 +4,7 @@ import { Domain, DomainHeader, DomainPanel, DomainNutshell, DomainLeak, Measures
 import DrilldownLink from '../helpers/drilldown-link';
 import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
 import { getMetricName } from '../helpers/metrics';
+import { formatMeasure, formatMeasureVariation } from '../../../helpers/measures';
 
 
 export const GeneralSize = React.createClass({
@@ -22,10 +23,10 @@ export const GeneralSize = React.createClass({
     return <DomainLeak>
       <MeasuresList>
         <Measure label={getMetricName('ncloc')}>
-          {window.formatMeasureVariation(this.props.leak['ncloc'], 'SHORT_INT')}
+          {formatMeasureVariation(this.props.leak['ncloc'], 'SHORT_INT')}
         </Measure>
         <Measure label={getMetricName('files')}>
-          {window.formatMeasureVariation(this.props.leak['files'], 'SHORT_INT')}
+          {formatMeasureVariation(this.props.leak['files'], 'SHORT_INT')}
         </Measure>
       </MeasuresList>
       {this.renderTimeline('after')}
@@ -42,12 +43,12 @@ export const GeneralSize = React.createClass({
           <MeasuresList>
             <Measure label={getMetricName('ncloc')}>
               <DrilldownLink component={this.props.component.key} metric="ncloc">
-                {window.formatMeasure(this.props.measures['ncloc'], 'SHORT_INT')}
+                {formatMeasure(this.props.measures['ncloc'], 'SHORT_INT')}
               </DrilldownLink>
             </Measure>
             <Measure label={getMetricName('files')}>
               <DrilldownLink component={this.props.component.key} metric="files">
-                {window.formatMeasure(this.props.measures['files'], 'SHORT_INT')}
+                {formatMeasure(this.props.measures['files'], 'SHORT_INT')}
               </DrilldownLink>
             </Measure>
           </MeasuresList>
index 0e2e1fa75c4e0a5486706804f36044a4560897e6..e6ea091ce87df3fd7142087a49b366a83d0afd98 100644 (file)
@@ -1,11 +1,13 @@
 import React from 'react';
+import { formatMeasureVariation } from '../../../helpers/measures';
+
 
 export default React.createClass({
   render() {
     if (this.props.value == null || isNaN(this.props.value)) {
       return null;
     }
-    let formatted = window.formatMeasureVariation(this.props.value, this.props.type);
+    let formatted = formatMeasureVariation(this.props.value, this.props.type);
     return <span>{formatted}</span>;
   }
 });
index dafc98a82c47b8615443964ec9150180d8f2906d..3d4eb5ec549f9994bec67b12c40e25b1e89e74b2 100644 (file)
@@ -1,11 +1,12 @@
 import React from 'react';
+import { formatMeasure } from '../../../helpers/measures';
 
 export default React.createClass({
   render() {
     if (this.props.value == null || isNaN(this.props.value)) {
       return null;
     }
-    let formatted = window.formatMeasure(this.props.value, this.props.type);
+    let formatted = formatMeasure(this.props.value, this.props.type);
     return <span>{formatted}</span>;
   }
 });
index e160019e9c6c432fd45b1979a093cd6bb3ec6ac2..5568dc5d2dcb4ef6f5931346163bf8ce20811388 100644 (file)
@@ -1,11 +1,12 @@
 import React from 'react';
+import { formatMeasure } from '../../../helpers/measures';
 
 export default React.createClass({
   render() {
     if (this.props.value == null || isNaN(this.props.value)) {
       return null;
     }
-    let formatted = window.formatMeasure(this.props.value, 'RATING');
+    let formatted = formatMeasure(this.props.value, 'RATING');
     let className = 'rating rating-' + formatted;
     return <span className={className}>{formatted}</span>;
   }
index 2d0905c8030ffe9e488c1b96affa00cd91eb669f..87c55e1de699ce86698da5f62a276ec7008a8d2c 100644 (file)
@@ -2,6 +2,8 @@ import React from 'react';
 import Assignee from '../../../components/shared/assignee-helper';
 import { DomainHeader } from '../domain/header';
 import { componentIssuesUrl } from '../../../helpers/Url';
+import { formatMeasure } from '../../../helpers/measures';
+
 
 export default class extends React.Component {
   render () {
@@ -12,7 +14,7 @@ export default class extends React.Component {
           <Assignee user={s.user}/>
         </td>
         <td className="thin text-right">
-          <a href={href}>{window.formatMeasure(s.count, 'SHORT_INT')}</a>
+          <a href={href}>{formatMeasure(s.count, 'SHORT_INT')}</a>
         </td>
       </tr>;
     });
index 5ccb3098b861a68be8c09819d4360818f6995a33..efd0e8e31845d63eb1e4e62441cdca444e25e4a6 100644 (file)
@@ -3,6 +3,8 @@ import React from 'react';
 import SeverityHelper from '../../../components/shared/severity-helper';
 import { DomainHeader } from '../domain/header';
 import { componentIssuesUrl } from '../../../helpers/Url';
+import { formatMeasure } from '../../../helpers/measures';
+
 
 export default class extends React.Component {
   sortedSeverities () {
@@ -18,7 +20,7 @@ export default class extends React.Component {
         </td>
         <td className="thin text-right">
           <a className="cell-link" href={href}>
-            {window.formatMeasure(s.count, 'SHORT_INT')}
+            {formatMeasure(s.count, 'SHORT_INT')}
           </a>
         </td>
       </tr>;
index 8c29d00f683fd7c91c38ab5d9e5b6b6b4ffccd0e..4d86824d25f0f8c80797724d466cd14596383045 100644 (file)
@@ -2,12 +2,14 @@ import React from 'react';
 import { DomainHeader } from '../domain/header';
 import { WordCloud } from '../../../components/charts/word-cloud';
 import { componentIssuesUrl } from '../../../helpers/Url';
+import { formatMeasure } from '../../../helpers/measures';
+
 
 export default class extends React.Component {
   renderWordCloud () {
     let tags = this.props.tags.map(tag => {
       let link = componentIssuesUrl(this.props.component.key, { resolved: 'false', tags: tag.val });
-      let tooltip = `Issues: ${window.formatMeasure(tag.count, 'SHORT_INT')}`;
+      let tooltip = `Issues: ${formatMeasure(tag.count, 'SHORT_INT')}`;
       return { text: tag.val, size: tag.count, link, tooltip };
     });
     return <WordCloud items={tags}/>;
index ac5796fa5841279dae6e745f1392a6adb8c6b1d1..9582e985f3c0f9a915bf3d8d2be110e79934de26 100644 (file)
@@ -2,6 +2,7 @@ import React from 'react';
 
 import { BarChart } from '../../../components/charts/bar-chart';
 import { getMeasures } from '../../../api/measures';
+import { formatMeasure } from '../../../helpers/measures';
 
 
 const HEIGHT = 120;
@@ -42,7 +43,7 @@ export class ComplexityDistribution extends React.Component {
 
     let xTicks = data.map(point => point.value);
 
-    let xValues = data.map(point => window.formatMeasure(point.y, 'INT'));
+    let xValues = data.map(point => formatMeasure(point.y, 'INT'));
 
     return <BarChart data={data}
                      xTicks={xTicks}
index 5e3acfaa4011b655e82050ab16d87db3e98eb60c..ab71de614fca9f1498d6217593df4af36a57ef75 100644 (file)
@@ -4,6 +4,7 @@ import React from 'react';
 import { BarChart } from '../../../components/charts/bar-chart';
 import { getMeasures } from '../../../api/measures';
 import { getLanguages } from '../../../api/languages';
+import { formatMeasure } from '../../../helpers/measures';
 
 
 const HEIGHT = 180;
@@ -56,7 +57,7 @@ export class LanguageDistribution extends React.Component {
 
     let xTicks = data.map(d => this.getLanguageName(d.lang));
 
-    let xValues = data.map(d => window.formatMeasure(d.y, 'INT'));
+    let xValues = data.map(d => formatMeasure(d.y, 'INT'));
 
     return <BarChart data={data}
                      xTicks={xTicks}
index 862c86f2d6c46f9d7139d3cf0201461d49a51760..61e34d23e1324eb60ed9b1d74b47019baa757206 100644 (file)
@@ -1,6 +1,8 @@
 import _ from 'underscore';
 import Marionette from 'backbone.marionette';
 import Template from './templates/quality-profiles-profile.hbs';
+import { formatMeasure } from '../../helpers/measures';
+
 
 export default Marionette.ItemView.extend({
   tagName: 'a',
@@ -33,7 +35,7 @@ export default Marionette.ItemView.extend({
 
   serializeData: function () {
     return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
-      projectCountFormatted: window.formatMeasure(this.model.get('projectCount'), 'INT')
+      projectCountFormatted: formatMeasure(this.model.get('projectCount'), 'INT')
     });
   }
 });
index 78778801674f1bfc3d780ea66dd7f176ad6457b3..e2a5110ee74063110521525fed37000e08e4a089 100644 (file)
@@ -2,9 +2,12 @@ import _ from 'underscore';
 import moment from 'moment';
 import Handlebars from 'hbsfy/runtime';
 import md5 from 'blueimp-md5';
+import { formatMeasure, formatMeasureVariation } from './measures';
+
 
 var defaultActions = ['comment', 'assign', 'assign_to_me', 'plan', 'set_severity', 'set_tags'];
 
+
 Handlebars.registerHelper('log', function () {
   /* eslint no-console: 0 */
   var args = Array.prototype.slice.call(arguments, 0, -1);
@@ -547,11 +550,11 @@ Handlebars.registerHelper('withSign', function (number) {
 });
 
 Handlebars.registerHelper('formatMeasure', function (measure, type) {
-  return window.formatMeasure(measure, type);
+  return formatMeasure(measure, type);
 });
 
 Handlebars.registerHelper('formatMeasureVariation', function (measure, type) {
-  return window.formatMeasureVariation(measure, type);
+  return formatMeasureVariation(measure, type);
 });
 
 Handlebars.registerHelper('dashboardL10n', function (dashboardName) {
diff --git a/server/sonar-web/src/main/js/helpers/measures.js b/server/sonar-web/src/main/js/helpers/measures.js
new file mode 100644 (file)
index 0000000..0e94332
--- /dev/null
@@ -0,0 +1,248 @@
+import numeral from 'numeral';
+
+
+/**
+ * Format a measure value for a given type
+ * @param {string|number} value
+ * @param {string} type
+ */
+export function formatMeasure (value, type) {
+  let formatter = getFormatter(type);
+  return useFormatter(value, formatter);
+}
+
+
+/**
+ * Format a measure variation for a given type
+ * @param {string|number} value
+ * @param {string} type
+ */
+export function formatMeasureVariation (value, type) {
+  let formatter = getVariationFormatter(type);
+  return useFormatter(value, formatter);
+}
+
+
+/*
+ * Helpers
+ */
+
+function useFormatter (value, formatter) {
+  return value != null && formatter != null ?
+      formatter(value) : null;
+}
+
+function getFormatter (type) {
+  const FORMATTERS = {
+    'INT': intFormatter,
+    'SHORT_INT': shortIntFormatter,
+    'FLOAT': floatFormatter,
+    'PERCENT': percentFormatter,
+    'WORK_DUR': durationFormatter,
+    'SHORT_WORK_DUR': shortDurationFormatter,
+    'RATING': ratingFormatter,
+    'LEVEL': levelFormatter,
+    'MILLISEC': millisecondsFormatter
+  };
+  return FORMATTERS[type] || noFormatter;
+}
+
+function getVariationFormatter (type) {
+  const FORMATTERS = {
+    'INT': intVariationFormatter,
+    'SHORT_INT': shortIntVariationFormatter,
+    'FLOAT': floatVariationFormatter,
+    'PERCENT': percentVariationFormatter,
+    'WORK_DUR': durationVariationFormatter,
+    'SHORT_WORK_DUR': shortDurationVariationFormatter,
+    'RATING': ratingFormatter,
+    'LEVEL': levelFormatter,
+    'MILLISEC': millisecondsFormatter
+  };
+  return FORMATTERS[type] || noFormatter;
+}
+
+
+/*
+ * Formatters
+ */
+
+
+function noFormatter (value) {
+  return value;
+}
+
+function intFormatter (value) {
+  return numeral(value).format('0,0');
+}
+
+function intVariationFormatter (value) {
+  return numeral(value).format('+0,0');
+}
+
+function shortIntFormatter (value) {
+  var format = '0,0';
+  if (value >= 1000) {
+    format = '0.[0]a';
+  }
+  if (value >= 10000) {
+    format = '0a';
+  }
+  return numeral(value).format(format);
+}
+
+function shortIntVariationFormatter (value) {
+  let formatted = shortIntFormatter(Math.abs(value));
+  return value < 0 ? `-${formatted}` : `+${formatted}`;
+}
+
+function floatFormatter (value) {
+  return numeral(value).format('0,0.0');
+}
+
+function floatVariationFormatter (value) {
+  return value === 0 ? '+0.0' : numeral(value).format('+0,0.0');
+}
+
+function percentFormatter (value) {
+  value = parseFloat(value);
+  return numeral(value / 100).format('0,0.0%');
+}
+
+function percentVariationFormatter (value) {
+  value = parseFloat(value);
+  return value === 0 ? '+0.0%' : numeral(value / 100).format('+0,0.0%');
+}
+
+function ratingFormatter (value) {
+  value = parseInt(value, 10);
+  return String.fromCharCode(97 + value - 1).toUpperCase();
+}
+
+function levelFormatter (value) {
+  var l10nKey = 'metric.level.' + value,
+      result = window.t(l10nKey);
+  // if couldn't translate, return the initial value
+  return l10nKey !== result ? result : value;
+}
+
+function millisecondsFormatter (value) {
+  const ONE_SECOND = 1000;
+  const ONE_MINUTE = 60 * ONE_SECOND;
+  if (value >= ONE_MINUTE) {
+    let minutes = Math.round(value / ONE_MINUTE);
+    return `${minutes}min`;
+  } else if (value >= ONE_SECOND) {
+    let seconds = Math.round(value / ONE_SECOND);
+    return `${seconds}s`;
+  } else {
+    return `${value}ms`;
+  }
+}
+
+
+/*
+ * Debt Formatters
+ */
+
+function shouldDisplayDays (days) {
+  return days > 0;
+}
+
+function shouldDisplayHours (days, hours) {
+  return hours > 0 && days < 10;
+}
+
+function shouldDisplayHoursInShortFormat (days, hours) {
+  return hours > 0 && days === 0;
+}
+
+function shouldDisplayMinutes (days, hours, minutes) {
+  return minutes > 0 && hours < 10 && days === 0;
+}
+
+function shouldDisplayMinutesInShortFormat (days, hours, minutes) {
+  return minutes > 0 && hours === 0 && days === 0;
+}
+
+function addSpaceIfNeeded (value) {
+  return value.length > 0 ? value + ' ' : value;
+}
+
+function formatDuration (isNegative, days, hours, minutes) {
+  var formatted = '';
+  if (shouldDisplayDays(days)) {
+    formatted += window.tp('work_duration.x_days', isNegative ? -1 * days : days);
+  }
+  if (shouldDisplayHours(days, hours)) {
+    formatted = addSpaceIfNeeded(formatted);
+    formatted += window.tp('work_duration.x_hours', isNegative && formatted.length === 0 ? -1 * hours : hours);
+  }
+  if (shouldDisplayMinutes(days, hours, minutes)) {
+    formatted = addSpaceIfNeeded(formatted);
+    formatted += window.tp('work_duration.x_minutes', isNegative && formatted.length === 0 ? -1 * minutes : minutes);
+  }
+  return formatted;
+}
+
+function formatDurationShort (isNegative, days, hours, minutes) {
+  var formatted = '';
+  if (shouldDisplayDays(days)) {
+    var formattedDays = formatMeasure(isNegative ? -1 * days : days, 'SHORT_INT');
+    formatted += window.tp('work_duration.x_days', formattedDays);
+  }
+  if (shouldDisplayHoursInShortFormat(days, hours)) {
+    formatted = addSpaceIfNeeded(formatted);
+    formatted += window.tp('work_duration.x_hours', isNegative && formatted.length === 0 ? -1 * hours : hours);
+  }
+  if (shouldDisplayMinutesInShortFormat(days, hours, minutes)) {
+    formatted = addSpaceIfNeeded(formatted);
+    formatted += window.tp('work_duration.x_minutes', isNegative && formatted.length === 0 ? -1 * minutes : minutes);
+  }
+  return formatted;
+}
+
+function durationFormatter (value) {
+  if (value === 0) {
+    return '0';
+  }
+  var hoursInDay = window.SS.hoursInDay,
+      isNegative = value < 0,
+      absValue = Math.abs(value);
+  var days = Math.round(absValue / hoursInDay / 60);
+  var remainingValue = absValue - days * hoursInDay * 60;
+  var hours = Math.round(remainingValue / 60);
+  remainingValue -= hours * 60;
+  return formatDuration(isNegative, days, hours, remainingValue);
+}
+
+function shortDurationFormatter (value) {
+  value = parseInt(value, 10);
+  if (value === 0) {
+    return '0';
+  }
+  var hoursInDay = window.SS.hoursInDay,
+      isNegative = value < 0,
+      absValue = Math.abs(value);
+  var days = Math.floor(absValue / hoursInDay / 60);
+  var remainingValue = absValue - days * hoursInDay * 60;
+  var hours = Math.floor(remainingValue / 60);
+  remainingValue -= hours * 60;
+  return formatDurationShort(isNegative, days, hours, remainingValue);
+}
+
+function durationVariationFormatter (value) {
+  if (value === 0) {
+    return '0';
+  }
+  var formatted = durationFormatter(value);
+  return formatted[0] !== '-' ? '+' + formatted : formatted;
+}
+
+function shortDurationVariationFormatter (value) {
+  if (value === 0) {
+    return '+0';
+  }
+  var formatted = shortDurationFormatter(value);
+  return formatted[0] !== '-' ? '+' + formatted : formatted;
+}
index 9613272304c4bb620f3100f12d02de98751e4499..eb55b2a74345de672c4ebdd06c3284b8de8e8eed 100644 (file)
@@ -262,306 +262,6 @@ function closeModalWindow () {
 
 
 
-/*
- * Measures
- */
-
-(function () {
-
-  function shortIntFormatter (value) {
-    var format = '0,0';
-    if (value >= 1000) {
-      format = '0.[0]a';
-    }
-    if (value >= 10000) {
-      format = '0a';
-    }
-    return numeral(value).format(format);
-  }
-
-  function shortIntVariationFormatter (value) {
-    if (value === 0) {
-      return '+0';
-    }
-    var format = '+0,0';
-    if (Math.abs(value) >= 1000) {
-      format = '+0.[0]a';
-    }
-    if (Math.abs(value) >= 10000) {
-      format = '+0a';
-    }
-    return numeral(value).format(format);
-  }
-
-  /**
-   * Check if days should be displayed for a work duration
-   * @param {number} days
-   * @returns {boolean}
-   */
-  function shouldDisplayDays (days) {
-    return days > 0;
-  }
-
-  /**
-   * Check if hours should be displayed for a work duration
-   * @param {number} days
-   * @param {number} hours
-   * @returns {boolean}
-   */
-  function shouldDisplayHours (days, hours) {
-    return hours > 0 && days < 10;
-  }
-
-  /**
-   * Check if hours should be displayed for a work duration
-   * @param {number} days
-   * @param {number} hours
-   * @returns {boolean}
-   */
-  function shouldDisplayHoursInShortFormat (days, hours) {
-    return hours > 0 && days === 0;
-  }
-
-  /**
-   * Check if minutes should be displayed for a work duration
-   * @param {number} days
-   * @param {number} hours
-   * @param {number} minutes
-   * @returns {boolean}
-   */
-  function shouldDisplayMinutes (days, hours, minutes) {
-    return minutes > 0 && hours < 10 && days === 0;
-  }
-
-  /**
-   * Check if minutes should be displayed for a work duration
-   * @param {number} days
-   * @param {number} hours
-   * @param {number} minutes
-   * @returns {boolean}
-   */
-  function shouldDisplayMinutesInShortFormat (days, hours, minutes) {
-    return minutes > 0 && hours === 0 && days === 0;
-  }
-
-  /**
-   * Add a space between units if needed
-   * @param {string} value
-   * @returns {string}
-   */
-  function addSpaceIfNeeded (value) {
-    return value.length > 0 ? value + ' ' : value;
-  }
-
-  /**
-   * Format a work duration based on parameters
-   * @param {bool} isNegative
-   * @param {number} days
-   * @param {number} hours
-   * @param {number} minutes
-   * @returns {string}
-   */
-  function formatDuration (isNegative, days, hours, minutes) {
-    var formatted = '';
-    if (shouldDisplayDays(days)) {
-      formatted += tp('work_duration.x_days', isNegative ? -1 * days : days);
-    }
-    if (shouldDisplayHours(days, hours)) {
-      formatted = addSpaceIfNeeded(formatted);
-      formatted += tp('work_duration.x_hours', isNegative && formatted.length === 0 ? -1 * hours : hours);
-    }
-    if (shouldDisplayMinutes(days, hours, minutes)) {
-      formatted = addSpaceIfNeeded(formatted);
-      formatted += tp('work_duration.x_minutes', isNegative && formatted.length === 0 ? -1 * minutes : minutes);
-    }
-    return formatted;
-  }
-
-  /**
-   * Format a work duration based on parameters
-   * @param {bool} isNegative
-   * @param {number} days
-   * @param {number} hours
-   * @param {number} minutes
-   * @returns {string}
-   */
-  function formatDurationShort (isNegative, days, hours, minutes) {
-    var formatted = '';
-    if (shouldDisplayDays(days)) {
-      var formattedDays = window.formatMeasure(isNegative ? -1 * days : days, 'SHORT_INT');
-      formatted += tp('work_duration.x_days', formattedDays);
-    }
-    if (shouldDisplayHoursInShortFormat(days, hours)) {
-      formatted = addSpaceIfNeeded(formatted);
-      formatted += tp('work_duration.x_hours', isNegative && formatted.length === 0 ? -1 * hours : hours);
-    }
-    if (shouldDisplayMinutesInShortFormat(days, hours, minutes)) {
-      formatted = addSpaceIfNeeded(formatted);
-      formatted += tp('work_duration.x_minutes', isNegative && formatted.length === 0 ? -1 * minutes : minutes);
-    }
-    return formatted;
-  }
-
-  /**
-   * Format a work duration measure
-   * @param {number} value
-   * @returns {string}
-   */
-  var durationFormatter = function (value) {
-    if (value === 0) {
-      return '0';
-    }
-    var hoursInDay = window.SS.hoursInDay || 8,
-        isNegative = value < 0,
-        absValue = Math.abs(value);
-    var days = Math.round(absValue / hoursInDay / 60);
-    var remainingValue = absValue - days * hoursInDay * 60;
-    var hours = Math.round(remainingValue / 60);
-    remainingValue -= hours * 60;
-    return formatDuration(isNegative, days, hours, remainingValue);
-  };
-
-  /**
-   * Format a work duration measure
-   * @param {number} value
-   * @returns {string}
-   */
-  var shortDurationFormatter = function (value) {
-    value = parseInt(value, 10);
-    if (value === 0) {
-      return '0';
-    }
-    var hoursInDay = window.SS.hoursInDay || 8,
-        isNegative = value < 0,
-        absValue = Math.abs(value);
-    var days = Math.floor(absValue / hoursInDay / 60);
-    var remainingValue = absValue - days * hoursInDay * 60;
-    var hours = Math.floor(remainingValue / 60);
-    remainingValue -= hours * 60;
-    return formatDurationShort(isNegative, days, hours, remainingValue);
-  };
-
-  /**
-   * Format a work duration variation
-   * @param {number} value
-   * @returns {string}
-   */
-  var durationVariationFormatter = function (value) {
-    if (value === 0) {
-      return '0';
-    }
-    var formatted = durationFormatter(value);
-    return formatted[0] !== '-' ? '+' + formatted : formatted;
-  };
-
-  /**
-   * Format a work duration variation
-   * @param {number} value
-   * @returns {string}
-   */
-  var shortDurationVariationFormatter = function (value) {
-    if (value === 0) {
-      return '+0';
-    }
-    var formatted = shortDurationFormatter(value);
-    return formatted[0] !== '-' ? '+' + formatted : formatted;
-  };
-
-  /**
-   * Format a rating measure
-   * @param {number} value
-   */
-  var ratingFormatter = function (value) {
-    value = parseInt(value, 10);
-    return String.fromCharCode(97 + value - 1).toUpperCase();
-  };
-
-
-  /**
-   * Format a level measure
-   * @param {number} value
-   */
-  var levelFormatter = function (value) {
-    var l10nKey = 'metric.level.' + value,
-        result = window.t(l10nKey);
-    // if couldn't translate, return the initial value
-    return l10nKey !== result ? result : value;
-  };
-
-
-  /**
-   * Format a milliseconds measure
-   * @param {number} value
-   */
-  var millisecondsFormatter = function (value) {
-    return value + ' ms';
-  };
-
-  /**
-   * Format a measure according to its type
-   * @param measure
-   * @param {string} type
-   * @returns {string|null}
-   */
-  window.formatMeasure = function (measure, type) {
-    var formatted = null,
-        formatters = {
-          'INT': function (value) {
-            return numeral(value).format('0,0');
-          },
-          'SHORT_INT': shortIntFormatter,
-          'FLOAT': function (value) {
-            return numeral(value).format('0,0.0');
-          },
-          'PERCENT': function (value) {
-            return numeral(+value / 100).format('0,0.0%');
-          },
-          'SHORT_PERCENT': function (value) {
-            return numeral(+value / 100).format('0,0%');
-          },
-          'WORK_DUR': durationFormatter,
-          'SHORT_WORK_DUR': shortDurationFormatter,
-          'RATING': ratingFormatter,
-          'LEVEL': levelFormatter,
-          'MILLISEC': millisecondsFormatter
-        };
-    if (measure != null && type != null) {
-      formatted = formatters[type] != null ? formatters[type](measure) : measure;
-    }
-    return formatted;
-  };
-
-  /**
-   * Format a measure variation according to its type
-   * @param measure
-   * @param {string} type
-   * @returns {string|null}
-   */
-  window.formatMeasureVariation = function (measure, type) {
-    var formatted = null,
-        formatters = {
-          'INT': function (value) {
-            return value === 0 ? '0' : numeral(value).format('+0,0');
-          },
-          'SHORT_INT': shortIntVariationFormatter,
-          'FLOAT': function (value) {
-            return value === 0 ? '+0.0' : numeral(value).format('+0,0.0');
-          },
-          'PERCENT': function (value) {
-            return value === 0 ? '+0.0%' : numeral(+value / 100).format('+0,0.0%');
-          },
-          'WORK_DUR': durationVariationFormatter,
-          'SHORT_WORK_DUR': shortDurationVariationFormatter
-        };
-    if (measure != null && type != null) {
-      formatted = formatters[type] != null ? formatters[type](measure) : measure;
-    }
-    return formatted;
-  };
-})();
-
-
-
 /*
  * Users
  */
index 9fee24ad9694ab3e98e05a2ff1c330326f694a03..32782666a5172e0ba6215c621bc494dda3041e5e 100644 (file)
@@ -15,6 +15,8 @@ import StatusesTemplate from './templates/widget-issue-filter-statuses.hbs';
 import LimitPartial from './templates/_widget-issue-filter-limit.hbs';
 import TotalPartial from './templates/_widget-issue-filter-total.hbs';
 
+import { formatMeasure } from '../../helpers/measures';
+
 import '../../helpers/handlebars-helpers';
 
 var FACET_LIMIT = 15,
@@ -228,7 +230,7 @@ Handlebars.registerHelper('issueFilterTotalLink', function (query, mode) {
 
 Handlebars.registerHelper('issueFilterValue', function (value, mode) {
   var formatter = mode === 'debt' ? 'SHORT_WORK_DUR' : 'SHORT_INT';
-  return window.formatMeasure(value, formatter);
+  return formatMeasure(value, formatter);
 });
 
 Handlebars.registerPartial('_widget-issue-filter-limit', LimitPartial);
index e7705ee3eebba1b282b7d12dd23622fbb676d761..14146be362f088201d239fcfc1f4b281098204e6 100644 (file)
@@ -45,171 +45,6 @@ describe.skip('Application', function () {
     });
   });
 
-  describe('Measures', function () {
-    var HOURS_IN_DAY = 8,
-        ONE_MINUTE = 1,
-        ONE_HOUR = ONE_MINUTE * 60,
-        ONE_DAY = HOURS_IN_DAY * ONE_HOUR;
-
-    before(function () {
-      window.messages = {
-        'work_duration.x_days': '{0}d',
-        'work_duration.x_hours': '{0}h',
-        'work_duration.x_minutes': '{0}min',
-        'work_duration.about': '~ {0}'
-      };
-      window.SS = { hoursInDay: HOURS_IN_DAY };
-    });
-
-    describe('#formatMeasure()', function () {
-      it('should format INT', function () {
-        assert.equal(window.formatMeasure(0, 'INT'), '0');
-        assert.equal(window.formatMeasure(1, 'INT'), '1');
-        assert.equal(window.formatMeasure(-5, 'INT'), '-5');
-        assert.equal(window.formatMeasure(999, 'INT'), '999');
-        assert.equal(window.formatMeasure(1000, 'INT'), '1,000');
-        assert.equal(window.formatMeasure(1529, 'INT'), '1,529');
-        assert.equal(window.formatMeasure(10000, 'INT'), '10,000');
-        assert.equal(window.formatMeasure(1234567890, 'INT'), '1,234,567,890');
-      });
-
-      it('should format SHORT_INT', function () {
-        assert.equal(window.formatMeasure(0, 'SHORT_INT'), '0');
-        assert.equal(window.formatMeasure(1, 'SHORT_INT'), '1');
-        assert.equal(window.formatMeasure(999, 'SHORT_INT'), '999');
-        assert.equal(window.formatMeasure(1000, 'SHORT_INT'), '1k');
-        assert.equal(window.formatMeasure(1529, 'SHORT_INT'), '1.5k');
-        assert.equal(window.formatMeasure(10000, 'SHORT_INT'), '10k');
-        assert.equal(window.formatMeasure(10678, 'SHORT_INT'), '11k');
-        assert.equal(window.formatMeasure(1234567890, 'SHORT_INT'), '1b');
-      });
-
-      it('should format FLOAT', function () {
-        assert.equal(window.formatMeasure(0.0, 'FLOAT'), '0.0');
-        assert.equal(window.formatMeasure(1.0, 'FLOAT'), '1.0');
-        assert.equal(window.formatMeasure(1.3, 'FLOAT'), '1.3');
-        assert.equal(window.formatMeasure(1.34, 'FLOAT'), '1.3');
-        assert.equal(window.formatMeasure(50.89, 'FLOAT'), '50.9');
-        assert.equal(window.formatMeasure(100.0, 'FLOAT'), '100.0');
-        assert.equal(window.formatMeasure(123.456, 'FLOAT'), '123.5');
-        assert.equal(window.formatMeasure(123456.7, 'FLOAT'), '123,456.7');
-        assert.equal(window.formatMeasure(1234567890.0, 'FLOAT'), '1,234,567,890.0');
-      });
-
-      it('should format PERCENT', function () {
-        assert.equal(window.formatMeasure(0.0, 'PERCENT'), '0.0%');
-        assert.equal(window.formatMeasure(1.0, 'PERCENT'), '1.0%');
-        assert.equal(window.formatMeasure(1.3, 'PERCENT'), '1.3%');
-        assert.equal(window.formatMeasure(1.34, 'PERCENT'), '1.3%');
-        assert.equal(window.formatMeasure(50.89, 'PERCENT'), '50.9%');
-        assert.equal(window.formatMeasure(100.0, 'PERCENT'), '100.0%');
-      });
-
-      it('should format WORK_DUR', function () {
-        assert.equal(window.formatMeasure(0, 'WORK_DUR'), '0');
-        assert.equal(window.formatMeasure(5 * ONE_DAY, 'WORK_DUR'), '5d');
-        assert.equal(window.formatMeasure(2 * ONE_HOUR, 'WORK_DUR'), '2h');
-        assert.equal(window.formatMeasure(ONE_MINUTE, 'WORK_DUR'), '1min');
-        assert.equal(window.formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR, 'WORK_DUR'), '5d 2h');
-        assert.equal(window.formatMeasure(2 * ONE_HOUR + ONE_MINUTE, 'WORK_DUR'), '2h 1min');
-        assert.equal(window.formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'WORK_DUR'), '5d 2h');
-        assert.equal(window.formatMeasure(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'WORK_DUR'), '15d');
-        assert.equal(window.formatMeasure(-5 * ONE_DAY, 'WORK_DUR'), '-5d');
-        assert.equal(window.formatMeasure(-2 * ONE_HOUR, 'WORK_DUR'), '-2h');
-        assert.equal(window.formatMeasure(-1 * ONE_MINUTE, 'WORK_DUR'), '-1min');
-      });
-
-      it('should format SHORT_WORK_DUR', function () {
-        assert.equal(window.formatMeasure(0, 'SHORT_WORK_DUR'), '0');
-        assert.equal(window.formatMeasure(5 * ONE_DAY, 'SHORT_WORK_DUR'), '5d');
-        assert.equal(window.formatMeasure(2 * ONE_HOUR, 'SHORT_WORK_DUR'), '2h');
-        assert.equal(window.formatMeasure(ONE_MINUTE, 'SHORT_WORK_DUR'), '1min');
-        assert.equal(window.formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR, 'SHORT_WORK_DUR'), '~ 5d');
-        assert.equal(window.formatMeasure(2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR'), '~ 2h');
-        assert.equal(window.formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR'), '~ 5d');
-        assert.equal(window.formatMeasure(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR'), '~ 15d');
-        assert.equal(window.formatMeasure(7 * ONE_MINUTE, 'SHORT_WORK_DUR'), '7min');
-        assert.equal(window.formatMeasure(-5 * ONE_DAY, 'SHORT_WORK_DUR'), '-5d');
-        assert.equal(window.formatMeasure(-2 * ONE_HOUR, 'SHORT_WORK_DUR'), '-2h');
-        assert.equal(window.formatMeasure(-1 * ONE_MINUTE, 'SHORT_WORK_DUR'), '-1min');
-
-        assert.equal(window.formatMeasure(1529 * ONE_DAY, 'SHORT_WORK_DUR'), '1.5kd');
-        assert.equal(window.formatMeasure(1234567 * ONE_DAY, 'SHORT_WORK_DUR'), '1md');
-        assert.equal(window.formatMeasure(1234567 * ONE_DAY + 2 * ONE_HOUR, 'SHORT_WORK_DUR'), '1md');
-      });
-
-      it('should format RATING', function () {
-        assert.equal(window.formatMeasure(1, 'RATING'), 'A');
-        assert.equal(window.formatMeasure(2, 'RATING'), 'B');
-        assert.equal(window.formatMeasure(3, 'RATING'), 'C');
-        assert.equal(window.formatMeasure(4, 'RATING'), 'D');
-        assert.equal(window.formatMeasure(5, 'RATING'), 'E');
-      });
-
-      it('should not format unknown type', function () {
-        assert.equal(window.formatMeasure('random value', 'RANDOM_TYPE'), 'random value');
-      });
-
-      it('should not fail without parameters', function () {
-        assert.isNull(window.formatMeasure());
-      });
-    });
-
-    describe('#formatMeasureVariation()', function () {
-      it('should format INT', function () {
-        assert.equal(window.formatMeasureVariation(0, 'INT'), '0');
-        assert.equal(window.formatMeasureVariation(1, 'INT'), '+1');
-        assert.equal(window.formatMeasureVariation(-1, 'INT'), '-1');
-        assert.equal(window.formatMeasureVariation(1529, 'INT'), '+1,529');
-        assert.equal(window.formatMeasureVariation(-1529, 'INT'), '-1,529');
-      });
-
-      it('should format SHORT_INT', function () {
-        assert.equal(window.formatMeasureVariation(0, 'SHORT_INT'), '0');
-        assert.equal(window.formatMeasureVariation(1, 'SHORT_INT'), '+1');
-        assert.equal(window.formatMeasureVariation(-1, 'SHORT_INT'), '-1');
-        assert.equal(window.formatMeasureVariation(1529, 'SHORT_INT'), '+1.5k');
-        assert.equal(window.formatMeasureVariation(-1529, 'SHORT_INT'), '-1.5k');
-        assert.equal(window.formatMeasureVariation(10678, 'SHORT_INT'), '+11k');
-        assert.equal(window.formatMeasureVariation(-10678, 'SHORT_INT'), '-11k');
-      });
-
-      it('should format FLOAT', function () {
-        assert.equal(window.formatMeasureVariation(0.0, 'FLOAT'), '0');
-        assert.equal(window.formatMeasureVariation(1.0, 'FLOAT'), '+1.0');
-        assert.equal(window.formatMeasureVariation(-1.0, 'FLOAT'), '-1.0');
-        assert.equal(window.formatMeasureVariation(50.89, 'FLOAT'), '+50.9');
-        assert.equal(window.formatMeasureVariation(-50.89, 'FLOAT'), '-50.9');
-      });
-
-      it('should format PERCENT', function () {
-        assert.equal(window.formatMeasureVariation(0.0, 'PERCENT'), '0%');
-        assert.equal(window.formatMeasureVariation(1.0, 'PERCENT'), '+1.0%');
-        assert.equal(window.formatMeasureVariation(-1.0, 'PERCENT'), '-1.0%');
-        assert.equal(window.formatMeasureVariation(50.89, 'PERCENT'), '+50.9%');
-        assert.equal(window.formatMeasureVariation(-50.89, 'PERCENT'), '-50.9%');
-      });
-
-      it('should format WORK_DUR', function () {
-        assert.equal(window.formatMeasureVariation(0, 'WORK_DUR'), '0');
-        assert.equal(window.formatMeasureVariation(5 * ONE_DAY, 'WORK_DUR'), '+5d');
-        assert.equal(window.formatMeasureVariation(2 * ONE_HOUR, 'WORK_DUR'), '+2h');
-        assert.equal(window.formatMeasureVariation(ONE_MINUTE, 'WORK_DUR'), '+1min');
-        assert.equal(window.formatMeasureVariation(-5 * ONE_DAY, 'WORK_DUR'), '-5d');
-        assert.equal(window.formatMeasureVariation(-2 * ONE_HOUR, 'WORK_DUR'), '-2h');
-        assert.equal(window.formatMeasureVariation(-1 * ONE_MINUTE, 'WORK_DUR'), '-1min');
-      });
-
-      it('should not format unknown type', function () {
-        assert.equal(window.formatMeasureVariation('random value', 'RANDOM_TYPE'), 'random value');
-      });
-
-      it('should not fail without parameters', function () {
-        assert.isNull(window.formatMeasureVariation());
-      });
-    });
-  });
-
   describe('Severity Comparators', function () {
     describe('#severityComparator', function () {
       it('should have correct order', function () {
diff --git a/server/sonar-web/tests/helpers/measures-test.js b/server/sonar-web/tests/helpers/measures-test.js
new file mode 100644 (file)
index 0000000..e302e2d
--- /dev/null
@@ -0,0 +1,226 @@
+import { expect } from 'chai';
+
+import { formatMeasure, formatMeasureVariation } from '../../src/main/js/helpers/measures';
+
+
+describe('Measures', function () {
+  var HOURS_IN_DAY = 8,
+      ONE_MINUTE = 1,
+      ONE_HOUR = ONE_MINUTE * 60,
+      ONE_DAY = HOURS_IN_DAY * ONE_HOUR;
+
+  before(function () {
+    window.messages = {
+      'work_duration.x_days': '{0}d',
+      'work_duration.x_hours': '{0}h',
+      'work_duration.x_minutes': '{0}min',
+      'work_duration.about': '~ {0}',
+      'metric.level.ERROR': 'Error',
+      'metric.level.WARN': 'Warning',
+      'metric.level.OK': 'Ok'
+    };
+    window.t = function() {
+      if (!window.messages) {
+        return window.translate.apply(this, arguments);
+      }
+      var args = Array.prototype.slice.call(arguments, 0),
+          key = args.join('.');
+      return window.messages[key] != null ? window.messages[key] : key;
+    };
+    window.tp = function () {
+      var args = Array.prototype.slice.call(arguments, 0),
+          key = args.shift(),
+          message = window.messages[key];
+      if (message) {
+        args.forEach(function (p, i) {
+          message = message.replace('{' + i + '}', p);
+        });
+      }
+      return message || (key + ' ' + args.join(' '));
+    };
+    window.SS = { hoursInDay: HOURS_IN_DAY };
+  });
+
+  describe('#formatMeasure()', function () {
+    it('should format INT', function () {
+      expect(formatMeasure(0, 'INT')).to.equal('0');
+      expect(formatMeasure(1, 'INT')).to.equal('1');
+      expect(formatMeasure(-5, 'INT')).to.equal('-5');
+      expect(formatMeasure(999, 'INT')).to.equal('999');
+      expect(formatMeasure(1000, 'INT')).to.equal('1,000');
+      expect(formatMeasure(1529, 'INT')).to.equal('1,529');
+      expect(formatMeasure(10000, 'INT')).to.equal('10,000');
+      expect(formatMeasure(1234567890, 'INT')).to.equal('1,234,567,890');
+    });
+
+    it('should format SHORT_INT', function () {
+      expect(formatMeasure(0, 'SHORT_INT')).to.equal('0');
+      expect(formatMeasure(1, 'SHORT_INT')).to.equal('1');
+      expect(formatMeasure(999, 'SHORT_INT')).to.equal('999');
+      expect(formatMeasure(1000, 'SHORT_INT')).to.equal('1k');
+      expect(formatMeasure(1529, 'SHORT_INT')).to.equal('1.5k');
+      expect(formatMeasure(10000, 'SHORT_INT')).to.equal('10k');
+      expect(formatMeasure(10678, 'SHORT_INT')).to.equal('11k');
+      expect(formatMeasure(1234567890, 'SHORT_INT')).to.equal('1b');
+    });
+
+    it('should format FLOAT', function () {
+      expect(formatMeasure(0.0, 'FLOAT')).to.equal('0.0');
+      expect(formatMeasure(1.0, 'FLOAT')).to.equal('1.0');
+      expect(formatMeasure(1.3, 'FLOAT')).to.equal('1.3');
+      expect(formatMeasure(1.34, 'FLOAT')).to.equal('1.3');
+      expect(formatMeasure(50.89, 'FLOAT')).to.equal('50.9');
+      expect(formatMeasure(100.0, 'FLOAT')).to.equal('100.0');
+      expect(formatMeasure(123.456, 'FLOAT')).to.equal('123.5');
+      expect(formatMeasure(123456.7, 'FLOAT')).to.equal('123,456.7');
+      expect(formatMeasure(1234567890.0, 'FLOAT')).to.equal('1,234,567,890.0');
+    });
+
+    it('should format PERCENT', function () {
+      expect(formatMeasure(0.0, 'PERCENT')).to.equal('0.0%');
+      expect(formatMeasure(1.0, 'PERCENT')).to.equal('1.0%');
+      expect(formatMeasure(1.3, 'PERCENT')).to.equal('1.3%');
+      expect(formatMeasure(1.34, 'PERCENT')).to.equal('1.3%');
+      expect(formatMeasure(50.89, 'PERCENT')).to.equal('50.9%');
+      expect(formatMeasure(100.0, 'PERCENT')).to.equal('100.0%');
+    });
+
+    it('should format WORK_DUR', function () {
+      expect(formatMeasure(0, 'WORK_DUR')).to.equal('0');
+      expect(formatMeasure(5 * ONE_DAY, 'WORK_DUR')).to.equal('5d');
+      expect(formatMeasure(2 * ONE_HOUR, 'WORK_DUR')).to.equal('2h');
+      expect(formatMeasure(ONE_MINUTE, 'WORK_DUR')).to.equal('1min');
+      expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR, 'WORK_DUR')).to.equal('5d 2h');
+      expect(formatMeasure(2 * ONE_HOUR + ONE_MINUTE, 'WORK_DUR')).to.equal('2h 1min');
+      expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'WORK_DUR')).to.equal('5d 2h');
+      expect(formatMeasure(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'WORK_DUR')).to.equal('15d');
+      expect(formatMeasure(-5 * ONE_DAY, 'WORK_DUR')).to.equal('-5d');
+      expect(formatMeasure(-2 * ONE_HOUR, 'WORK_DUR')).to.equal('-2h');
+      expect(formatMeasure(-1 * ONE_MINUTE, 'WORK_DUR')).to.equal('-1min');
+    });
+
+    it('should format SHORT_WORK_DUR', function () {
+      expect(formatMeasure(0, 'SHORT_WORK_DUR')).to.equal('0');
+      expect(formatMeasure(5 * ONE_DAY, 'SHORT_WORK_DUR')).to.equal('5d');
+      expect(formatMeasure(2 * ONE_HOUR, 'SHORT_WORK_DUR')).to.equal('2h');
+      expect(formatMeasure(ONE_MINUTE, 'SHORT_WORK_DUR')).to.equal('1min');
+      expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR, 'SHORT_WORK_DUR')).to.equal('5d');
+      expect(formatMeasure(2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).to.equal('2h');
+      expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).to.equal('5d');
+      expect(formatMeasure(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).to.equal('15d');
+      expect(formatMeasure(7 * ONE_MINUTE, 'SHORT_WORK_DUR')).to.equal('7min');
+      expect(formatMeasure(-5 * ONE_DAY, 'SHORT_WORK_DUR')).to.equal('-5d');
+      expect(formatMeasure(-2 * ONE_HOUR, 'SHORT_WORK_DUR')).to.equal('-2h');
+      expect(formatMeasure(-1 * ONE_MINUTE, 'SHORT_WORK_DUR')).to.equal('-1min');
+
+      expect(formatMeasure(1529 * ONE_DAY, 'SHORT_WORK_DUR')).to.equal('1.5kd');
+      expect(formatMeasure(1234567 * ONE_DAY, 'SHORT_WORK_DUR')).to.equal('1md');
+      expect(formatMeasure(1234567 * ONE_DAY + 2 * ONE_HOUR, 'SHORT_WORK_DUR')).to.equal('1md');
+    });
+
+    it('should format RATING', function () {
+      expect(formatMeasure(1, 'RATING')).to.equal('A');
+      expect(formatMeasure(2, 'RATING')).to.equal('B');
+      expect(formatMeasure(3, 'RATING')).to.equal('C');
+      expect(formatMeasure(4, 'RATING')).to.equal('D');
+      expect(formatMeasure(5, 'RATING')).to.equal('E');
+    });
+
+    it('should format LEVEL', function () {
+      expect(formatMeasure('ERROR', 'LEVEL')).to.equal('Error');
+      expect(formatMeasure('WARN', 'LEVEL')).to.equal('Warning');
+      expect(formatMeasure('OK', 'LEVEL')).to.equal('Ok');
+      expect(formatMeasure('UNKNOWN', 'LEVEL')).to.equal('UNKNOWN');
+    });
+
+    it('should format MILLISEC', function () {
+      expect(formatMeasure(0, 'MILLISEC')).to.equal('0ms');
+      expect(formatMeasure(1, 'MILLISEC')).to.equal('1ms');
+      expect(formatMeasure(173, 'MILLISEC')).to.equal('173ms');
+      expect(formatMeasure(3649, 'MILLISEC')).to.equal('4s');
+      expect(formatMeasure(893481, 'MILLISEC')).to.equal('15min');
+      expect(formatMeasure(17862325, 'MILLISEC')).to.equal('298min');
+    });
+
+    it('should not format unknown type', function () {
+      expect(formatMeasure('random value', 'RANDOM_TYPE')).to.equal('random value');
+    });
+
+    it('should not fail without parameters', function () {
+      expect(formatMeasure()).to.be.null;
+    });
+  });
+
+  describe('#formatMeasureVariation()', function () {
+    it('should format INT', function () {
+      expect(formatMeasureVariation(0, 'INT')).to.equal('+0');
+      expect(formatMeasureVariation(1, 'INT')).to.equal('+1');
+      expect(formatMeasureVariation(-1, 'INT')).to.equal('-1');
+      expect(formatMeasureVariation(1529, 'INT')).to.equal('+1,529');
+      expect(formatMeasureVariation(-1529, 'INT')).to.equal('-1,529');
+    });
+
+    it('should format SHORT_INT', function () {
+      expect(formatMeasureVariation(0, 'SHORT_INT')).to.equal('+0');
+      expect(formatMeasureVariation(1, 'SHORT_INT')).to.equal('+1');
+      expect(formatMeasureVariation(-1, 'SHORT_INT')).to.equal('-1');
+      expect(formatMeasureVariation(1529, 'SHORT_INT')).to.equal('+1.5k');
+      expect(formatMeasureVariation(-1529, 'SHORT_INT')).to.equal('-1.5k');
+      expect(formatMeasureVariation(10678, 'SHORT_INT')).to.equal('+11k');
+      expect(formatMeasureVariation(-10678, 'SHORT_INT')).to.equal('-11k');
+    });
+
+    it('should format FLOAT', function () {
+      expect(formatMeasureVariation(0.0, 'FLOAT')).to.equal('+0.0');
+      expect(formatMeasureVariation(1.0, 'FLOAT')).to.equal('+1.0');
+      expect(formatMeasureVariation(-1.0, 'FLOAT')).to.equal('-1.0');
+      expect(formatMeasureVariation(50.89, 'FLOAT')).to.equal('+50.9');
+      expect(formatMeasureVariation(-50.89, 'FLOAT')).to.equal('-50.9');
+    });
+
+    it('should format PERCENT', function () {
+      expect(formatMeasureVariation(0.0, 'PERCENT')).to.equal('+0.0%');
+      expect(formatMeasureVariation(1.0, 'PERCENT')).to.equal('+1.0%');
+      expect(formatMeasureVariation(-1.0, 'PERCENT')).to.equal('-1.0%');
+      expect(formatMeasureVariation(50.89, 'PERCENT')).to.equal('+50.9%');
+      expect(formatMeasureVariation(-50.89, 'PERCENT')).to.equal('-50.9%');
+    });
+
+    it('should format WORK_DUR', function () {
+      expect(formatMeasureVariation(0, 'WORK_DUR')).to.equal('0');
+      expect(formatMeasureVariation(5 * ONE_DAY, 'WORK_DUR')).to.equal('+5d');
+      expect(formatMeasureVariation(2 * ONE_HOUR, 'WORK_DUR')).to.equal('+2h');
+      expect(formatMeasureVariation(ONE_MINUTE, 'WORK_DUR')).to.equal('+1min');
+      expect(formatMeasureVariation(-5 * ONE_DAY, 'WORK_DUR')).to.equal('-5d');
+      expect(formatMeasureVariation(-2 * ONE_HOUR, 'WORK_DUR')).to.equal('-2h');
+      expect(formatMeasureVariation(-1 * ONE_MINUTE, 'WORK_DUR')).to.equal('-1min');
+    });
+
+    it('should format SHORT_WORK_DUR', function () {
+      expect(formatMeasureVariation(0, 'SHORT_WORK_DUR')).to.equal('+0');
+      expect(formatMeasureVariation(5 * ONE_DAY, 'SHORT_WORK_DUR')).to.equal('+5d');
+      expect(formatMeasureVariation(2 * ONE_HOUR, 'SHORT_WORK_DUR')).to.equal('+2h');
+      expect(formatMeasureVariation(ONE_MINUTE, 'SHORT_WORK_DUR')).to.equal('+1min');
+      expect(formatMeasureVariation(5 * ONE_DAY + 2 * ONE_HOUR, 'SHORT_WORK_DUR')).to.equal('+5d');
+      expect(formatMeasureVariation(2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).to.equal('+2h');
+      expect(formatMeasureVariation(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).to.equal('+5d');
+      expect(formatMeasureVariation(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).to.equal('+15d');
+      expect(formatMeasureVariation(7 * ONE_MINUTE, 'SHORT_WORK_DUR')).to.equal('+7min');
+      expect(formatMeasureVariation(-5 * ONE_DAY, 'SHORT_WORK_DUR')).to.equal('-5d');
+      expect(formatMeasureVariation(-2 * ONE_HOUR, 'SHORT_WORK_DUR')).to.equal('-2h');
+      expect(formatMeasureVariation(-1 * ONE_MINUTE, 'SHORT_WORK_DUR')).to.equal('-1min');
+
+      expect(formatMeasureVariation(1529 * ONE_DAY, 'SHORT_WORK_DUR')).to.equal('+1.5kd');
+      expect(formatMeasureVariation(1234567 * ONE_DAY, 'SHORT_WORK_DUR')).to.equal('+1md');
+      expect(formatMeasureVariation(1234567 * ONE_DAY + 2 * ONE_HOUR, 'SHORT_WORK_DUR')).to.equal('+1md');
+    });
+
+    it('should not format unknown type', function () {
+      expect(formatMeasureVariation('random value', 'RANDOM_TYPE')).to.equal('random value');
+    });
+
+    it('should not fail without parameters', function () {
+      expect(formatMeasureVariation()).to.be.null;
+    });
+  });
+});