aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main
diff options
context:
space:
mode:
authorViktor Vorona <viktor.vorona@sonarsource.com>2024-04-23 16:41:37 +0200
committerMatteo Mara <matteo.mara@sonarsource.com>2024-04-30 10:59:03 +0200
commit57b2e33c8c321d3297631de46e1fda374e3b17d8 (patch)
tree35a0fea4ea65bf6099fabbac25b5bdc8f6a6789e /server/sonar-web/src/main
parenta5599ff112362c822a669d965c6a9fc85d633e37 (diff)
downloadsonarqube-57b2e33c8c321d3297631de46e1fda374e3b17d8.tar.gz
sonarqube-57b2e33c8c321d3297631de46e1fda374e3b17d8.zip
SONAR-22049 Align formatMeasure
Diffstat (limited to 'server/sonar-web/src/main')
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/branch-like/QualityGateStatus.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingTime.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChartView.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/ColorRatingsLegend.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/sidebar/SubnavigationMeasureValue.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/TotalEffort.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacet.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacetFooter.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/utils.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/AnalysisVariations.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/BranchMetaTopBar.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/QualityGateSimplifiedCondition.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/MeasuresCardNumber.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/MeasuresCardPercent.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/pullRequests/BranchQualityGateConditions.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestMetaTopBar.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/utils.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformationRenderer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/SecurityReviewFilter.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/CaycCondition.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValue.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValueDescription.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/StatusUpdateSuccessModal.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/system/utils.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx6
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/DataTableModal.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx3
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/charts/ColorBoxLegend.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/charts/LanguageDistribution.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/common/PageCounter.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/controls/ListFooter.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueChangelogDiff.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/IssueChangelogDiff-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/measure/Measure.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/measure/MeasureIndicator.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/measure/RatingTooltipContent.tsx3
-rw-r--r--server/sonar-web/src/main/js/components/ui/FilesCounter.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/ui/Rating.tsx2
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts189
-rw-r--r--server/sonar-web/src/main/js/helpers/measures.ts287
-rw-r--r--server/sonar-web/src/main/js/sonar-aligned/helpers/__tests__/measures-test.ts207
-rw-r--r--server/sonar-web/src/main/js/sonar-aligned/helpers/measures.ts310
66 files changed, 604 insertions, 541 deletions
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/QualityGateStatus.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/QualityGateStatus.tsx
index 534262f9d62..4b6cad57e4a 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/QualityGateStatus.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/QualityGateStatus.tsx
@@ -21,7 +21,7 @@ import classNames from 'classnames';
import { QualityGateIndicator } from 'design-system';
import React from 'react';
import { translateWithParameters } from '../../../../../helpers/l10n';
-import { formatMeasure } from '../../../../../helpers/measures';
+import { formatMeasure } from '../../../../../sonar-aligned/helpers/measures';
import { BranchLike } from '../../../../../types/branch-like';
import { MetricType } from '../../../../../types/metrics';
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 ecb186bf184..1cbd7c8ca53 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
@@ -31,9 +31,9 @@ import MetaLink from '../../../components/common/MetaLink';
import Tooltip from '../../../components/controls/Tooltip';
import DateFromNow from '../../../components/intl/DateFromNow';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
import { orderLinks } from '../../../helpers/projectLinks';
import { getProjectUrl } from '../../../helpers/urls';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricType } from '../../../types/metrics';
import { MyProject, ProjectLink, Status } from '../../../types/types';
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingTime.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingTime.tsx
index d52332bbc76..7b4fc52c0f1 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingTime.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingTime.tsx
@@ -20,8 +20,8 @@
import { HelperHintIcon } from 'design-system';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
import HelpTooltip from '../../../sonar-aligned/components/controls/HelpTooltip';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
// Do not display the pending time for values smaller than this threshold (in ms)
const MIN_PENDING_TIME_THRESHOLD = 1000;
diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx b/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx
index b4b21a0fab7..a4a3a41824a 100644
--- a/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/ComponentMeasure.tsx
@@ -35,9 +35,9 @@ import {
import { translateWithParameters } from '../../../helpers/l10n';
import {
areCCTMeasuresComputed as areCCTMeasuresComputedFn,
- formatMeasure,
isDiffMetric,
} from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { isApplication, isProject } from '../../../types/component';
import { MetricKey, MetricType } from '../../../types/metrics';
import { Metric, Status, ComponentMeasure as TypeComponentMeasure } from '../../../types/types';
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx
index 8f1343a4efc..9137773c91e 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/BulkChangeModal.tsx
@@ -22,7 +22,7 @@ import * as React from 'react';
import { Profile, bulkActivateRules, bulkDeactivateRules } from '../../../api/quality-profiles';
import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { Languages } from '../../../types/languages';
import { MetricType } from '../../../types/metrics';
import { Dict } from '../../../types/types';
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx
index 485e8261e07..4156a90e8ab 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/Facet.tsx
@@ -23,7 +23,7 @@ import { orderBy, sortBy, without } from 'lodash';
import * as React from 'react';
import Tooltip from '../../../components/controls/Tooltip';
import { translate } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricType } from '../../../types/metrics';
import { Dict } from '../../../types/types';
import { FacetItemsList } from '../../issues/sidebar/FacetItemsList';
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
index f723b214a48..dbfd31d4765 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
@@ -29,8 +29,8 @@ import withAvailableFeatures, {
import Tooltip from '../../../components/controls/Tooltip';
import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
import { translate } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
import { getIssuesUrl } from '../../../helpers/urls';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { Feature } from '../../../types/features';
import { FacetName } from '../../../types/issues';
import { MetricType } from '../../../types/metrics';
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx
index 4fd76d7d88b..b772df0a850 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx
@@ -25,8 +25,9 @@ import LanguageDistribution from '../../../components/charts/LanguageDistributio
import Tooltip from '../../../components/controls/Tooltip';
import Measure from '../../../components/measure/Measure';
import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
+import { isDiffMetric } from '../../../helpers/measures';
import { getMeasureHistoryUrl } from '../../../helpers/urls';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { BranchLike } from '../../../types/branch-like';
import { ComponentQualifier } from '../../../types/component';
import { MetricKey, MetricType } from '../../../types/metrics';
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChartView.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChartView.tsx
index 28bee63698c..0dd396ef30d 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChartView.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChartView.tsx
@@ -33,10 +33,11 @@ import {
translate,
translateWithParameters,
} from '../../../helpers/l10n';
-import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
+import { isDiffMetric } from '../../../helpers/measures';
import { isDefined } from '../../../helpers/types';
import { getComponentDrilldownUrl } from '../../../helpers/urls';
import HelpTooltip from '../../../sonar-aligned/components/controls/HelpTooltip';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { BranchLike } from '../../../types/branch-like';
import { isProject, isView } from '../../../types/component';
import { MetricKey } from '../../../types/metrics';
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ColorRatingsLegend.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ColorRatingsLegend.tsx
index 7b521886e71..9eecb2e95ca 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ColorRatingsLegend.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ColorRatingsLegend.tsx
@@ -20,7 +20,7 @@
import { ColorFilterOption, ColorsLegend } from 'design-system';
import * as React from 'react';
import { translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricType } from '../../../types/metrics';
export interface ColorRatingsLegendProps {
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx
index 6db5c021d3a..d7370ee781f 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx
@@ -24,7 +24,8 @@ import ListFooter from '../../../components/controls/ListFooter';
import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers';
import { KeyboardKeys } from '../../../helpers/keycodes';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure, isDiffMetric, isPeriodBestValue } from '../../../helpers/measures';
+import { isDiffMetric, isPeriodBestValue } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { BranchLike } from '../../../types/branch-like';
import { MeasurePageView } from '../../../types/measures';
import { MetricType } from '../../../types/metrics';
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.tsx
index e4354dbf8c9..ff45c4a3d6d 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/MeasureCell.tsx
@@ -21,7 +21,8 @@ import { MetricsRatingBadge, NumericalCell, RatingLabel } from 'design-system';
import * as React from 'react';
import Measure from '../../../components/measure/Measure';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure, getCCTMeasureValue, isDiffMetric } from '../../../helpers/measures';
+import { getCCTMeasureValue, isDiffMetric } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricType } from '../../../types/metrics';
import { ComponentMeasureEnhanced, MeasureEnhanced, Metric } from '../../../types/types';
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx
index d26a3a160b7..b00b48be0c6 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx
@@ -36,8 +36,9 @@ import ColorBoxLegend from '../../../components/charts/ColorBoxLegend';
import ColorGradientLegend from '../../../components/charts/ColorGradientLegend';
import { getComponentMeasureUniqueKey } from '../../../helpers/component';
import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
-import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
+import { isDiffMetric } from '../../../helpers/measures';
import { isDefined } from '../../../helpers/types';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricKey, MetricType } from '../../../types/metrics';
import { ComponentMeasureEnhanced, ComponentMeasureIntern, Metric } from '../../../types/types';
import EmptyResult from './EmptyResult';
diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/SubnavigationMeasureValue.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/SubnavigationMeasureValue.tsx
index ab344f0ff6b..0b9a0e545c8 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/SubnavigationMeasureValue.tsx
+++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/SubnavigationMeasureValue.tsx
@@ -21,7 +21,8 @@ import { MetricsRatingBadge, Note, RatingLabel } from 'design-system';
import React from 'react';
import Measure from '../../../components/measure/Measure';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
+import { isDiffMetric } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricType } from '../../../types/metrics';
import { MeasureEnhanced } from '../../../types/types';
diff --git a/server/sonar-web/src/main/js/apps/issues/components/TotalEffort.tsx b/server/sonar-web/src/main/js/apps/issues/components/TotalEffort.tsx
index 2b7fc276ccc..34fc771e667 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/TotalEffort.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/TotalEffort.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { translate } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
export default function TotalEffort({ effort }: { effort: number }) {
return (
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx
index 829e23792f4..edd5ace2cd7 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.tsx
@@ -27,7 +27,7 @@ import DateFromNow from '../../../components/intl/DateFromNow';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import { parseDate } from '../../../helpers/dates';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricType } from '../../../types/metrics';
import { Component, Dict } from '../../../types/types';
import { Query } from '../utils';
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacet.tsx
index 4552da2afc0..36cf186598c 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacet.tsx
@@ -23,9 +23,9 @@ import * as React from 'react';
import ListFooter from '../../../components/controls/ListFooter';
import Tooltip from '../../../components/controls/Tooltip';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
import { queriesEqual } from '../../../helpers/query';
import { isDefined } from '../../../helpers/types';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricType } from '../../../types/metrics';
import { Dict, Paging, RawQuery } from '../../../types/types';
import { FacetItemsList } from './FacetItemsList';
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacetFooter.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacetFooter.tsx
index 635394cc2d8..14a054bf641 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacetFooter.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacetFooter.tsx
@@ -21,7 +21,7 @@ import { useTheme } from '@emotion/react';
import { DiscreetLink, Theme, themeColor } from 'design-system';
import * as React from 'react';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricType } from '../../../types/metrics';
export interface Props {
diff --git a/server/sonar-web/src/main/js/apps/issues/utils.ts b/server/sonar-web/src/main/js/apps/issues/utils.ts
index 67e936bce8b..1e0da76fa23 100644
--- a/server/sonar-web/src/main/js/apps/issues/utils.ts
+++ b/server/sonar-web/src/main/js/apps/issues/utils.ts
@@ -20,7 +20,6 @@
import { intersection, isArray, uniq } from 'lodash';
import { getUsers } from '../../api/users';
import { DEFAULT_ISSUES_QUERY } from '../../components/shared/utils';
-import { formatMeasure } from '../../helpers/measures';
import {
cleanQuery,
parseAsArray,
@@ -35,6 +34,7 @@ import {
} from '../../helpers/query';
import { get, save } from '../../helpers/storage';
import { isDefined } from '../../helpers/types';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import {
CleanCodeAttributeCategory,
SoftwareImpactSeverity,
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/AnalysisVariations.tsx b/server/sonar-web/src/main/js/apps/overview/branches/AnalysisVariations.tsx
index d15d4413a7b..6a372a5c18f 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/AnalysisVariations.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/AnalysisVariations.tsx
@@ -21,7 +21,7 @@ import styled from '@emotion/styled';
import { TrendDirection, TrendIcon, TrendType, themeColor } from 'design-system';
import React from 'react';
import { FormattedMessage } from 'react-intl';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricType } from '../../../types/metrics';
import { AnalysisMeasuresVariations } from '../../../types/project-activity';
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchMetaTopBar.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchMetaTopBar.tsx
index ab91aa2661c..f3df27076ac 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/BranchMetaTopBar.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchMetaTopBar.tsx
@@ -23,7 +23,8 @@ import { useIntl } from 'react-intl';
import { getCurrentPage } from '../../../app/components/nav/component/utils';
import ComponentReportActions from '../../../components/controls/ComponentReportActions';
import HomePageSelect from '../../../components/controls/HomePageSelect';
-import { findMeasure, formatMeasure } from '../../../helpers/measures';
+import { findMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { Branch } from '../../../types/branch-like';
import { MetricKey, MetricType } from '../../../types/metrics';
import { Component, MeasureEnhanced } from '../../../types/types';
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx
index 94aae3bb987..3bf4823d83f 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx
@@ -34,13 +34,14 @@ import React from 'react';
import { useIntl } from 'react-intl';
import { getLeakValue } from '../../../components/measure/utils';
import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
-import { findMeasure, formatMeasure, formatRating } from '../../../helpers/measures';
+import { findMeasure, formatRating } from '../../../helpers/measures';
import {
CodeScope,
getComponentIssuesUrl,
getComponentSecurityHotspotsUrl,
} from '../../../helpers/urls';
import { getBranchLikeQuery } from '../../../sonar-aligned/helpers/branch-like';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { Branch } from '../../../types/branch-like';
import { isApplication } from '../../../types/component';
import { IssueStatus } from '../../../types/issues';
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx
index a594bfbf0ed..e803855e937 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx
@@ -26,13 +26,14 @@ import {
} from 'design-system';
import * as React from 'react';
import { useIntl } from 'react-intl';
-import { findMeasure, formatMeasure, formatRating } from '../../../helpers/measures';
+import { findMeasure, formatRating } from '../../../helpers/measures';
import {
CodeScope,
getComponentIssuesUrl,
getComponentSecurityHotspotsUrl,
} from '../../../helpers/urls';
import { getBranchLikeQuery } from '../../../sonar-aligned/helpers/branch-like';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { Branch } from '../../../types/branch-like';
import { SoftwareQuality } from '../../../types/clean-code-taxonomy';
import { isApplication } from '../../../types/component';
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx
index d07769a9c80..182b19781c6 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateCondition.tsx
@@ -28,7 +28,7 @@ import {
propsToIssueParams,
} from '../../../components/shared/utils';
import { translate } from '../../../helpers/l10n';
-import { formatMeasure, isDiffMetric, localizeMetric } from '../../../helpers/measures';
+import { isDiffMetric, localizeMetric } from '../../../helpers/measures';
import { getOperatorLabel } from '../../../helpers/qualityGates';
import {
getComponentDrilldownUrl,
@@ -36,6 +36,7 @@ import {
getComponentSecurityHotspotsUrl,
} from '../../../helpers/urls';
import { getBranchLikeQuery } from '../../../sonar-aligned/helpers/branch-like';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { BranchLike } from '../../../types/branch-like';
import { IssueType } from '../../../types/issues';
import { MetricKey, MetricType } from '../../../types/metrics';
@@ -139,7 +140,7 @@ export default class QualityGateCondition extends React.PureComponent<Props> {
if (metric.type !== MetricType.Rating) {
const actual = (condition.period ? measure.period?.value : measure.value) as string;
const formattedValue = formatMeasure(actual, metric.type, {
- decimal: 2,
+ decimals: 1,
omitExtraDecimalZeros: metric.type === MetricType.Percent,
});
return `${formattedValue} ${subText}`;
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateSimplifiedCondition.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateSimplifiedCondition.tsx
index 4c1b3e9ba6d..e31e94ea4ee 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateSimplifiedCondition.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateSimplifiedCondition.tsx
@@ -21,9 +21,10 @@ import { Highlight, LinkBox } from 'design-system';
import * as React from 'react';
import { propsToIssueParams } from '../../../components/shared/utils';
import { translate } from '../../../helpers/l10n';
-import { formatMeasure, isDiffMetric, localizeMetric } from '../../../helpers/measures';
+import { isDiffMetric, localizeMetric } from '../../../helpers/measures';
import { getComponentIssuesUrl } from '../../../helpers/urls';
import { getBranchLikeQuery } from '../../../sonar-aligned/helpers/branch-like';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { BranchLike } from '../../../types/branch-like';
import { MetricKey, MetricType } from '../../../types/metrics';
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx
index 4e6799fd95d..0f3620e3d90 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureBreakdownCard.tsx
@@ -23,8 +23,8 @@ import { DiscreetLinkBox, Tooltip, themeColor, themeContrast } from 'design-syst
import * as React from 'react';
import { useIntl } from 'react-intl';
import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
-import { formatMeasure } from '../../../helpers/measures';
import { getComponentIssuesUrl } from '../../../helpers/urls';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { Branch } from '../../../types/branch-like';
import { SoftwareImpactSeverity, SoftwareQuality } from '../../../types/clean-code-taxonomy';
import { MetricType } from '../../../types/metrics';
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx
index 0da8bccbde3..40814c2a15e 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx
@@ -28,9 +28,9 @@ import {
SOFTWARE_QUALITIES_METRIC_KEYS_MAP,
getIssueTypeBySoftwareQuality,
} from '../../../helpers/issues';
-import { formatMeasure } from '../../../helpers/measures';
import { isDefined } from '../../../helpers/types';
import { getComponentIssuesUrl } from '../../../helpers/urls';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { Branch } from '../../../types/branch-like';
import {
SoftwareImpactMeasureData,
diff --git a/server/sonar-web/src/main/js/apps/overview/components/MeasuresCardNumber.tsx b/server/sonar-web/src/main/js/apps/overview/components/MeasuresCardNumber.tsx
index feea138e269..7d186fe1fd6 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/MeasuresCardNumber.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/MeasuresCardNumber.tsx
@@ -21,7 +21,7 @@ import { LightLabel, TextError } from 'design-system';
import * as React from 'react';
import { useIntl } from 'react-intl';
import { To } from 'react-router-dom';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricKey, MetricType } from '../../../types/metrics';
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
import { Status, getConditionRequiredLabel } from '../utils';
diff --git a/server/sonar-web/src/main/js/apps/overview/components/MeasuresCardPercent.tsx b/server/sonar-web/src/main/js/apps/overview/components/MeasuresCardPercent.tsx
index 8bdb401d6f4..4875e5a0788 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/MeasuresCardPercent.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/MeasuresCardPercent.tsx
@@ -30,8 +30,9 @@ import { FormattedMessage, useIntl } from 'react-intl';
import { To } from 'react-router-dom';
import { duplicationRatingConverter, getLeakValue } from '../../../components/measure/utils';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { findMeasure, formatMeasure, localizeMetric } from '../../../helpers/measures';
+import { findMeasure, localizeMetric } from '../../../helpers/measures';
import { getComponentDrilldownUrl } from '../../../helpers/urls';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { BranchLike } from '../../../types/branch-like';
import { MetricKey, MetricType } from '../../../types/metrics';
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/BranchQualityGateConditions.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/BranchQualityGateConditions.tsx
index 9fe6fc70bb2..f2586295e7e 100644
--- a/server/sonar-web/src/main/js/apps/overview/pullRequests/BranchQualityGateConditions.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/BranchQualityGateConditions.tsx
@@ -27,13 +27,14 @@ import {
propsToIssueParams,
} from '../../../components/shared/utils';
import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
-import { formatMeasure, getShortType, isDiffMetric } from '../../../helpers/measures';
+import { getShortType, isDiffMetric } from '../../../helpers/measures';
import {
getComponentDrilldownUrl,
getComponentIssuesUrl,
getComponentSecurityHotspotsUrl,
} from '../../../helpers/urls';
import { getBranchLikeQuery } from '../../../sonar-aligned/helpers/branch-like';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { BranchLike } from '../../../types/branch-like';
import { IssueType } from '../../../types/issues';
import { MetricType } from '../../../types/metrics';
diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx
index aaed573861a..613c243c5d4 100644
--- a/server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/IssueMeasuresCard.tsx
@@ -35,9 +35,10 @@ import * as React from 'react';
import { useIntl } from 'react-intl';
import { getLeakValue } from '../../../components/measure/utils';
import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
-import { findMeasure, formatMeasure } from '../../../helpers/measures';
+import { findMeasure } from '../../../helpers/measures';
import { getComponentIssuesUrl } from '../../../helpers/urls';
import { getBranchLikeQuery } from '../../../sonar-aligned/helpers/branch-like';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { PullRequest } from '../../../types/branch-like';
import { MetricKey, MetricType } from '../../../types/metrics';
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestMetaTopBar.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestMetaTopBar.tsx
index 0ce4a8aae1f..aae9b023155 100644
--- a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestMetaTopBar.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestMetaTopBar.tsx
@@ -22,7 +22,8 @@ import React from 'react';
import { useIntl } from 'react-intl';
import CurrentBranchLikeMergeInformation from '../../../app/components/nav/component/branch-like/CurrentBranchLikeMergeInformation';
import { getLeakValue } from '../../../components/measure/utils';
-import { findMeasure, formatMeasure } from '../../../helpers/measures';
+import { findMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { PullRequest } from '../../../types/branch-like';
import { MetricKey, MetricType } from '../../../types/metrics';
import { MeasureEnhanced } from '../../../types/types';
diff --git a/server/sonar-web/src/main/js/apps/overview/utils.tsx b/server/sonar-web/src/main/js/apps/overview/utils.tsx
index bcb1431ffab..eb0c2112807 100644
--- a/server/sonar-web/src/main/js/apps/overview/utils.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/utils.tsx
@@ -22,8 +22,8 @@ import React from 'react';
import { IntlShape } from 'react-intl';
import { ISSUETYPE_METRIC_KEYS_MAP } from '../../helpers/issues';
import { translate } from '../../helpers/l10n';
-import { formatMeasure } from '../../helpers/measures';
import { parseAsString } from '../../helpers/query';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import { SoftwareQuality } from '../../types/clean-code-taxonomy';
import { IssueType } from '../../types/issues';
import { MetricKey, MetricType } from '../../types/metrics';
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformationRenderer.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformationRenderer.tsx
index 7ab9e1ecca2..566a9332e3f 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformationRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/LifetimeInformationRenderer.tsx
@@ -21,7 +21,7 @@ import { Link, Spinner } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { translate } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
export interface LifetimeInformationRendererProps {
branchAndPullRequestLifeTimeInDays?: string;
diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx
index 7593c2a9b32..3172136ad9c 100644
--- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx
+++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx
@@ -20,8 +20,9 @@
import { DrilldownLink, Note, SizeIndicator, SubHeading } from 'design-system';
import * as React from 'react';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
-import { formatMeasure, localizeMetric } from '../../../../helpers/measures';
+import { localizeMetric } from '../../../../helpers/measures';
import { getComponentDrilldownUrl } from '../../../../helpers/urls';
+import { formatMeasure } from '../../../../sonar-aligned/helpers/measures';
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey, MetricType } from '../../../../types/metrics';
import { Component, Measure } from '../../../../types/types';
diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx
index 1c254aec9c4..71896a62567 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx
@@ -43,9 +43,9 @@ import DateFromNow from '../../../../components/intl/DateFromNow';
import DateTimeFormatter from '../../../../components/intl/DateTimeFormatter';
import Measure from '../../../../components/measure/Measure';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
-import { formatMeasure } from '../../../../helpers/measures';
import { isDefined } from '../../../../helpers/types';
import { getProjectUrl } from '../../../../helpers/urls';
+import { formatMeasure } from '../../../../sonar-aligned/helpers/measures';
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey, MetricType } from '../../../../types/metrics';
import { Status } from '../../../../types/types';
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx b/server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx
index 6dac1d3015d..f33656bbd5f 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/RatingFacet.tsx
@@ -20,7 +20,7 @@
import { MetricsRatingBadge, RatingEnum } from 'design-system';
import * as React from 'react';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricType } from '../../../types/metrics';
import { RawQuery } from '../../../types/types';
import { Facet } from '../types';
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SecurityReviewFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/SecurityReviewFilter.tsx
index 9764cea8aa6..b84363490de 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/SecurityReviewFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/SecurityReviewFilter.tsx
@@ -20,7 +20,7 @@
import { MetricsRatingBadge, RatingEnum } from 'design-system';
import * as React from 'react';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricType } from '../../../types/metrics';
import { Dict, RawQuery } from '../../../types/types';
import { Facet } from '../types';
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/CaycCondition.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/CaycCondition.tsx
index 4d523433faa..ad7f681dfb3 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/CaycCondition.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CaycCondition.tsx
@@ -23,8 +23,8 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
import { translate } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
import DocHelpTooltip from '../../../sonar-aligned/components/controls/DocHelpTooltip';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricKey } from '../../../types/metrics';
import { Condition, Dict, Metric } from '../../../types/types';
import { getCaycConditionMetadata, getLocalizedMetricNameNoDiffMetric } from '../utils';
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValue.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValue.tsx
index 82d94ed1f16..703830027cc 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValue.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValue.tsx
@@ -21,7 +21,7 @@ import styled from '@emotion/styled';
import classNames from 'classnames';
import { themeColor } from 'design-system';
import * as React from 'react';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { Condition, Metric } from '../../../types/types';
import { getCorrectCaycCondition } from '../utils';
import ConditionValueDescription from './ConditionValueDescription';
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValueDescription.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValueDescription.tsx
index a93ee48e131..e03131227e3 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValueDescription.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValueDescription.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricKey } from '../../../types/metrics';
import { Condition, Metric } from '../../../types/types';
import { GreenColorText } from './ConditionValue';
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.tsx
index 8cc304097cd..c9a9bd316a5 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.tsx
@@ -20,9 +20,9 @@
import { ContentCell, Link, Note, NumericalCell, TableRow } from 'design-system';
import * as React from 'react';
import { translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
import { isDefined } from '../../../helpers/types';
import { getRulesUrl } from '../../../helpers/urls';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricType } from '../../../types/metrics';
import { RulesFacetName } from '../../../types/rules';
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 45e15ddabb8..5db04574648 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
@@ -24,8 +24,8 @@ import { useIntl } from 'react-intl';
import { listRules } from '../../../api/rules';
import { toShortISO8601String } from '../../../helpers/dates';
import { translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
import { getRulesUrl } from '../../../helpers/urls';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { MetricType } from '../../../types/metrics';
import { Rule, RuleActivation } from '../../../types/types';
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/StatusUpdateSuccessModal.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/StatusUpdateSuccessModal.tsx
index 1ee9b32e651..7f081e17679 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/StatusUpdateSuccessModal.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/StatusUpdateSuccessModal.tsx
@@ -21,8 +21,8 @@ import { ButtonPrimary, ButtonSecondary, Checkbox, Modal, Note } from 'design-sy
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
import { save } from '../../../helpers/storage';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { HotspotStatusOption } from '../../../types/security-hotspots';
import { SHOW_STATUS_DIALOG_STORAGE_KEY } from '../constants';
diff --git a/server/sonar-web/src/main/js/apps/system/utils.ts b/server/sonar-web/src/main/js/apps/system/utils.ts
index 350b250031b..5168c128f3b 100644
--- a/server/sonar-web/src/main/js/apps/system/utils.ts
+++ b/server/sonar-web/src/main/js/apps/system/utils.ts
@@ -18,8 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { each, memoize, omit, omitBy, pickBy, sortBy } from 'lodash';
-import { formatMeasure } from '../../helpers/measures';
import { cleanQuery, parseAsArray, parseAsString, serializeStringArray } from '../../helpers/query';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import {
RawQuery,
SysInfoAppNode,
diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx
index cd77dae024b..5d03123739f 100644
--- a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx
+++ b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveryItem.tsx
@@ -20,7 +20,7 @@
import { CodeSnippet, Spinner } from 'design-system';
import * as React from 'react';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { WebhookDelivery } from '../../../types/webhook';
import { formatPayload } from '../utils';
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
index fbe658763a6..c2372d53b3c 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
@@ -44,10 +44,7 @@ import {
SOFTWARE_QUALITIES_METRIC_KEYS_MAP,
getIssueTypeBySoftwareQuality,
} from '../../helpers/issues';
-import {
- areCCTMeasuresComputed as areCCTMeasuresComputedFn,
- formatMeasure,
-} from '../../helpers/measures';
+import { areCCTMeasuresComputed as areCCTMeasuresComputedFn } from '../../helpers/measures';
import { collapsedDirFromPath, fileFromPath } from '../../helpers/path';
import { omitNil } from '../../helpers/request';
import { getBaseUrl } from '../../helpers/system';
@@ -59,6 +56,7 @@ import {
getComponentSecurityHotspotsUrl,
} from '../../helpers/urls';
import { getBranchLikeQuery } from '../../sonar-aligned/helpers/branch-like';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import type { BranchLike } from '../../types/branch-like';
import { ComponentQualifier } from '../../types/component';
import { IssueType } from '../../types/issues';
diff --git a/server/sonar-web/src/main/js/components/activity-graph/DataTableModal.tsx b/server/sonar-web/src/main/js/components/activity-graph/DataTableModal.tsx
index 732e8b18dd7..d95c51a2f28 100644
--- a/server/sonar-web/src/main/js/components/activity-graph/DataTableModal.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/DataTableModal.tsx
@@ -23,7 +23,7 @@ import { filter, slice, sortBy } from 'lodash';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { translate, translateWithParameters } from '../../helpers/l10n';
-import { formatMeasure } from '../../helpers/measures';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import { ParsedAnalysis, Serie } from '../../types/project-activity';
import DateFormatter from '../intl/DateFormatter';
import TimeFormatter from '../intl/TimeFormatter';
diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx
index c715da2013b..675e2b0895e 100644
--- a/server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx
@@ -23,7 +23,8 @@ import * as React from 'react';
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
import { AdvancedTimeline } from '../../components/charts/AdvancedTimeline';
import { translate } from '../../helpers/l10n';
-import { formatMeasure, getShortType } from '../../helpers/measures';
+import { getShortType } from '../../helpers/measures';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import { MeasureHistory, ParsedAnalysis, Serie } from '../../types/project-activity';
import ModalButton from '../controls/ModalButton';
import DataTableModal from './DataTableModal';
diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx
index 8f289f9605a..65a080ae412 100644
--- a/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx
@@ -20,7 +20,7 @@
import { TableSeparator } from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
-import { formatMeasure } from '../../helpers/measures';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import { MetricKey, MetricType } from '../../types/metrics';
import { MeasureHistory } from '../../types/project-activity';
diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx
index 4c1a88bfe6c..2d14453306f 100644
--- a/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx
@@ -20,7 +20,7 @@
import { TableSeparator } from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
-import { formatMeasure } from '../../helpers/measures';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import { MetricKey, MetricType } from '../../types/metrics';
import { MeasureHistory } from '../../types/project-activity';
diff --git a/server/sonar-web/src/main/js/components/charts/ColorBoxLegend.tsx b/server/sonar-web/src/main/js/components/charts/ColorBoxLegend.tsx
index d700ae06b8c..633ab763798 100644
--- a/server/sonar-web/src/main/js/components/charts/ColorBoxLegend.tsx
+++ b/server/sonar-web/src/main/js/components/charts/ColorBoxLegend.tsx
@@ -20,7 +20,7 @@
import styled from '@emotion/styled';
import { ScaleLinear, ScaleOrdinal } from 'd3-scale';
import * as React from 'react';
-import { formatMeasure } from '../../helpers/measures';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
interface Props {
colorNA?: string;
diff --git a/server/sonar-web/src/main/js/components/charts/LanguageDistribution.tsx b/server/sonar-web/src/main/js/components/charts/LanguageDistribution.tsx
index c8b3a8bd9c2..d3fe35ae3a6 100644
--- a/server/sonar-web/src/main/js/components/charts/LanguageDistribution.tsx
+++ b/server/sonar-web/src/main/js/components/charts/LanguageDistribution.tsx
@@ -22,7 +22,7 @@ import { sortBy } from 'lodash';
import * as React from 'react';
import withLanguagesContext from '../../app/components/languages/withLanguagesContext';
import { translate } from '../../helpers/l10n';
-import { formatMeasure } from '../../helpers/measures';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import { Languages } from '../../types/languages';
import { MetricType } from '../../types/metrics';
diff --git a/server/sonar-web/src/main/js/components/common/PageCounter.tsx b/server/sonar-web/src/main/js/components/common/PageCounter.tsx
index d48ddc16bcc..2f20c7216fc 100644
--- a/server/sonar-web/src/main/js/components/common/PageCounter.tsx
+++ b/server/sonar-web/src/main/js/components/common/PageCounter.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { formatMeasure } from '../../helpers/measures';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import { MetricType } from '../../types/metrics';
export interface PageCounterProps {
diff --git a/server/sonar-web/src/main/js/components/controls/ListFooter.tsx b/server/sonar-web/src/main/js/components/controls/ListFooter.tsx
index 70071fb047d..b393b36ada3 100644
--- a/server/sonar-web/src/main/js/components/controls/ListFooter.tsx
+++ b/server/sonar-web/src/main/js/components/controls/ListFooter.tsx
@@ -22,7 +22,7 @@ import classNames from 'classnames';
import { ButtonSecondary, Spinner, themeColor } from 'design-system';
import * as React from 'react';
import { translate, translateWithParameters } from '../../helpers/l10n';
-import { formatMeasure } from '../../helpers/measures';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import { MetricType } from '../../types/metrics';
export interface ListFooterProps {
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueChangelogDiff.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueChangelogDiff.tsx
index 037798b92f1..638c8e3349c 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueChangelogDiff.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueChangelogDiff.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure } from '../../../helpers/measures';
+import { formatMeasure } from '../../../sonar-aligned/helpers/measures';
import { IssueChangelogDiff as TypeIssueChangelogDiff } from '../../../types/types';
export interface IssueChangelogDiffProps {
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueChangelogDiff-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueChangelogDiff-test.tsx
index f533304d5ac..de956d27a58 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueChangelogDiff-test.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueChangelogDiff-test.tsx
@@ -23,7 +23,7 @@ import { mockIssueChangelogDiff } from '../../../../helpers/mocks/issues';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import IssueChangelogDiff, { IssueChangelogDiffProps } from '../IssueChangelogDiff';
-jest.mock('../../../../helpers/measures', () => ({
+jest.mock('../../../../sonar-aligned/helpers/measures', () => ({
formatMeasure: jest
.fn()
.mockImplementation((value: string, type: string) => `formatted.${value}.as.${type}`),
diff --git a/server/sonar-web/src/main/js/components/measure/Measure.tsx b/server/sonar-web/src/main/js/components/measure/Measure.tsx
index 0e0f1f0e140..6f9a7c647b2 100644
--- a/server/sonar-web/src/main/js/components/measure/Measure.tsx
+++ b/server/sonar-web/src/main/js/components/measure/Measure.tsx
@@ -21,14 +21,14 @@ import { MetricsRatingBadge, QualityGateIndicator, RatingLabel } from 'design-sy
import * as React from 'react';
import Tooltip from '../../components/controls/Tooltip';
import { translate, translateWithParameters } from '../../helpers/l10n';
-import { formatMeasure } from '../../helpers/measures';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import { MetricType } from '../../types/metrics';
import { Status } from '../../types/types';
import RatingTooltipContent from './RatingTooltipContent';
interface Props {
className?: string;
- decimals?: number | null;
+ decimals?: number;
metricKey: string;
metricType: string;
small?: boolean;
diff --git a/server/sonar-web/src/main/js/components/measure/MeasureIndicator.tsx b/server/sonar-web/src/main/js/components/measure/MeasureIndicator.tsx
index 9889e139348..2e384775ab3 100644
--- a/server/sonar-web/src/main/js/components/measure/MeasureIndicator.tsx
+++ b/server/sonar-web/src/main/js/components/measure/MeasureIndicator.tsx
@@ -24,14 +24,14 @@ import {
RatingEnum,
} from 'design-system';
import * as React from 'react';
-import { formatMeasure } from '../../helpers/measures';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import { MetricKey, MetricType } from '../../types/metrics';
import Measure from './Measure';
import { duplicationRatingConverter } from './utils';
interface Props {
className?: string;
- decimals?: number | null;
+ decimals?: number;
metricKey: string;
metricType: string;
small?: boolean;
diff --git a/server/sonar-web/src/main/js/components/measure/RatingTooltipContent.tsx b/server/sonar-web/src/main/js/components/measure/RatingTooltipContent.tsx
index 26dd0f4b542..d83276ea6bf 100644
--- a/server/sonar-web/src/main/js/components/measure/RatingTooltipContent.tsx
+++ b/server/sonar-web/src/main/js/components/measure/RatingTooltipContent.tsx
@@ -20,13 +20,14 @@
import * as React from 'react';
import withAppStateContext from '../../app/components/app-state/withAppStateContext';
import { translate, translateWithParameters } from '../../helpers/l10n';
-import { formatMeasure, isDiffMetric } from '../../helpers/measures';
+import { isDiffMetric } from '../../helpers/measures';
import {
DIFF_METRIC_PREFIX_LENGTH,
GRID_INDEX_OFFSET,
PERCENT_MULTIPLIER,
getMaintainabilityGrid,
} from '../../helpers/ratings';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import { AppState } from '../../types/appstate';
import { MetricKey, MetricType } from '../../types/metrics';
import { GlobalSettingKeys } from '../../types/settings';
diff --git a/server/sonar-web/src/main/js/components/ui/FilesCounter.tsx b/server/sonar-web/src/main/js/components/ui/FilesCounter.tsx
index 6916b574d5e..37ed3bd3d99 100644
--- a/server/sonar-web/src/main/js/components/ui/FilesCounter.tsx
+++ b/server/sonar-web/src/main/js/components/ui/FilesCounter.tsx
@@ -22,8 +22,8 @@ import classNames from 'classnames';
import { Note, themeColor } from 'design-system';
import React from 'react';
import { translate } from '../../helpers/l10n';
-import { formatMeasure } from '../../helpers/measures';
import { isDefined } from '../../helpers/types';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import { MetricType } from '../../types/metrics';
interface Props {
diff --git a/server/sonar-web/src/main/js/components/ui/Rating.tsx b/server/sonar-web/src/main/js/components/ui/Rating.tsx
index 449a268d995..055e99dc9eb 100644
--- a/server/sonar-web/src/main/js/components/ui/Rating.tsx
+++ b/server/sonar-web/src/main/js/components/ui/Rating.tsx
@@ -20,7 +20,7 @@
import classNames from 'classnames';
import * as React from 'react';
import { translate, translateWithParameters } from '../../helpers/l10n';
-import { formatMeasure } from '../../helpers/measures';
+import { formatMeasure } from '../../sonar-aligned/helpers/measures';
import { MetricType } from '../../types/metrics';
import './Rating.css';
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts
index 820f4a6636b..06083a1ee23 100644
--- a/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts
+++ b/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts
@@ -17,49 +17,16 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { MetricKey, MetricType } from '../../types/metrics';
-import { Dict } from '../../types/types';
+import { MetricKey } from '../../types/metrics';
import { CCT_SOFTWARE_QUALITY_METRICS } from '../constants';
-import { getMessages } from '../l10nBundle';
import {
areCCTMeasuresComputed,
enhanceConditionWithMeasure,
- formatMeasure,
isPeriodBestValue,
} from '../measures';
import { mockQualityGateStatusCondition } from '../mocks/quality-gates';
import { mockMeasure, mockMeasureEnhanced, mockMetric } from '../testMocks';
-jest.unmock('../l10n');
-
-jest.mock('../l10nBundle', () => ({
- getCurrentLocale: jest.fn().mockReturnValue('us'),
- getMessages: jest.fn().mockReturnValue({}),
-}));
-
-const resetMessages = (messages: Dict<string>) =>
- (getMessages as jest.Mock).mockReturnValue(messages);
-
-beforeAll(() => {
- resetMessages({
- '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',
- 'short_number_suffix.g': 'G',
- 'short_number_suffix.k': 'k',
- 'short_number_suffix.m': 'M',
- });
-});
-
-const HOURS_IN_DAY = 8;
-const ONE_MINUTE = 1;
-const ONE_HOUR = ONE_MINUTE * 60;
-const ONE_DAY = HOURS_IN_DAY * ONE_HOUR;
-
describe('enhanceConditionWithMeasure', () => {
it('should correctly map enhance conditions with measure data', () => {
const measures = [
@@ -112,160 +79,6 @@ describe('isPeriodBestValue', () => {
});
});
-describe('#formatMeasure()', () => {
- it('should format INT', () => {
- expect(formatMeasure(0, MetricType.Integer)).toBe('0');
- expect(formatMeasure(1, MetricType.Integer)).toBe('1');
- expect(formatMeasure(-5, MetricType.Integer)).toBe('-5');
- expect(formatMeasure(999, MetricType.Integer)).toBe('999');
- expect(formatMeasure(1000, MetricType.Integer)).toBe('1,000');
- expect(formatMeasure(1529, MetricType.Integer)).toBe('1,529');
- expect(formatMeasure(10000, MetricType.Integer)).toBe('10,000');
- expect(formatMeasure(1234567890, MetricType.Integer)).toBe('1,234,567,890');
- });
-
- it('should format SHORT_INT', () => {
- expect(formatMeasure(0, MetricType.ShortInteger)).toBe('0');
- expect(formatMeasure(1, MetricType.ShortInteger)).toBe('1');
- expect(formatMeasure(999, MetricType.ShortInteger)).toBe('999');
- expect(formatMeasure(1000, MetricType.ShortInteger)).toBe('1k');
- expect(formatMeasure(1529, MetricType.ShortInteger)).toBe('1.5k');
- expect(formatMeasure(10000, MetricType.ShortInteger)).toBe('10k');
- expect(formatMeasure(10678, MetricType.ShortInteger)).toBe('11k');
- expect(formatMeasure(9467890, MetricType.ShortInteger)).toBe('9.5M');
- expect(formatMeasure(994567890, MetricType.ShortInteger)).toBe('995M');
- expect(formatMeasure(999000001, MetricType.ShortInteger)).toBe('999M');
- expect(formatMeasure(999567890, MetricType.ShortInteger)).toBe('1G');
- expect(formatMeasure(1234567890, MetricType.ShortInteger)).toBe('1.2G');
- expect(formatMeasure(11234567890, MetricType.ShortInteger)).toBe('11G');
- });
-
- it('should format FLOAT', () => {
- expect(formatMeasure(0.0, 'FLOAT')).toBe('0.0');
- expect(formatMeasure(1.0, 'FLOAT')).toBe('1.0');
- expect(formatMeasure(1.3, 'FLOAT')).toBe('1.3');
- expect(formatMeasure(1.34, 'FLOAT')).toBe('1.34');
- expect(formatMeasure(50.89, 'FLOAT')).toBe('50.89');
- expect(formatMeasure(100.0, 'FLOAT')).toBe('100.0');
- expect(formatMeasure(123.456, 'FLOAT')).toBe('123.456');
- expect(formatMeasure(123456.7, 'FLOAT')).toBe('123,456.7');
- expect(formatMeasure(1234567890.0, 'FLOAT')).toBe('1,234,567,890.0');
- });
-
- it('should respect FLOAT precision', () => {
- expect(formatMeasure(0.1, 'FLOAT')).toBe('0.1');
- expect(formatMeasure(0.12, 'FLOAT')).toBe('0.12');
- expect(formatMeasure(0.12345, 'FLOAT')).toBe('0.12345');
- expect(formatMeasure(0.123456, 'FLOAT')).toBe('0.12346');
- });
-
- it('should format PERCENT', () => {
- expect(formatMeasure(0.0, MetricType.Percent)).toBe('0.0%');
- expect(formatMeasure(1.0, MetricType.Percent)).toBe('1.0%');
- expect(formatMeasure(1.3, MetricType.Percent)).toBe('1.3%');
- expect(formatMeasure(1.34, MetricType.Percent)).toBe('1.3%');
- expect(formatMeasure(50.89, MetricType.Percent)).toBe('50.9%');
- expect(formatMeasure(100.0, MetricType.Percent)).toBe('100%');
- expect(formatMeasure(50.89, MetricType.Percent, { decimals: 0 })).toBe('50.9%');
- expect(formatMeasure(50.89, MetricType.Percent, { decimals: 1 })).toBe('50.9%');
- expect(formatMeasure(50.89, MetricType.Percent, { decimals: 2 })).toBe('50.89%');
- expect(formatMeasure(50.89, MetricType.Percent, { decimals: 3 })).toBe('50.890%');
- expect(
- formatMeasure(50, MetricType.Percent, { decimals: 0, omitExtraDecimalZeros: true }),
- ).toBe('50.0%');
- expect(
- formatMeasure(50, MetricType.Percent, { decimals: 1, omitExtraDecimalZeros: true }),
- ).toBe('50.0%');
- expect(
- formatMeasure(50, MetricType.Percent, { decimals: 3, omitExtraDecimalZeros: true }),
- ).toBe('50.0%');
- expect(
- formatMeasure(50.89, MetricType.Percent, { decimals: 3, omitExtraDecimalZeros: true }),
- ).toBe('50.89%');
- });
-
- it('should format WORK_DUR', () => {
- expect(formatMeasure(0, 'WORK_DUR')).toBe('0');
- expect(formatMeasure(5 * ONE_DAY, 'WORK_DUR')).toBe('5d');
- expect(formatMeasure(2 * ONE_HOUR, 'WORK_DUR')).toBe('2h');
- expect(formatMeasure(40 * ONE_MINUTE, 'WORK_DUR')).toBe('40min');
- expect(formatMeasure(ONE_MINUTE, 'WORK_DUR')).toBe('1min');
- expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR, 'WORK_DUR')).toBe('5d 2h');
- expect(formatMeasure(2 * ONE_HOUR + ONE_MINUTE, 'WORK_DUR')).toBe('2h 1min');
- expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'WORK_DUR')).toBe('5d 2h');
- expect(formatMeasure(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'WORK_DUR')).toBe('15d');
- expect(formatMeasure(-5 * ONE_DAY, 'WORK_DUR')).toBe('-5d');
- expect(formatMeasure(-2 * ONE_HOUR, 'WORK_DUR')).toBe('-2h');
- expect(formatMeasure(-1 * ONE_MINUTE, 'WORK_DUR')).toBe('-1min');
- });
-
- it('should format SHORT_WORK_DUR', () => {
- expect(formatMeasure(0, MetricType.ShortWorkDuration)).toBe('0');
- expect(formatMeasure(5 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('5d');
- expect(formatMeasure(2 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('2h');
- expect(formatMeasure(ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('1min');
- expect(formatMeasure(40 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('40min');
- expect(formatMeasure(58 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('1h');
- expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('5d');
- expect(formatMeasure(2 * ONE_HOUR + ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('2h');
- expect(formatMeasure(ONE_HOUR + 55 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('2h');
- expect(formatMeasure(3 * ONE_DAY + 6 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('4d');
- expect(formatMeasure(7 * ONE_HOUR + 59 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('1d');
- expect(
- formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, MetricType.ShortWorkDuration),
- ).toBe('5d');
- expect(
- formatMeasure(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, MetricType.ShortWorkDuration),
- ).toBe('15d');
- expect(formatMeasure(7 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('7min');
- expect(formatMeasure(-5 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('-5d');
- expect(formatMeasure(-2 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('-2h');
- expect(formatMeasure(-1 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('-1min');
-
- expect(formatMeasure(1529 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('1.5kd');
- expect(formatMeasure(1234567 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('1.2Md');
- expect(formatMeasure(12345670 * ONE_DAY + 4 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe(
- '12Md',
- );
- });
-
- it('should format RATING', () => {
- expect(formatMeasure(1, MetricType.Rating)).toBe('A');
- expect(formatMeasure(2, MetricType.Rating)).toBe('B');
- expect(formatMeasure(3, MetricType.Rating)).toBe('C');
- expect(formatMeasure(4, MetricType.Rating)).toBe('D');
- expect(formatMeasure(5, MetricType.Rating)).toBe('E');
- });
-
- it('should format LEVEL', () => {
- expect(formatMeasure('ERROR', MetricType.Level)).toBe('Error');
- expect(formatMeasure('WARN', MetricType.Level)).toBe('Warning');
- expect(formatMeasure('OK', MetricType.Level)).toBe('Ok');
- expect(formatMeasure('UNKNOWN', MetricType.Level)).toBe('UNKNOWN');
- });
-
- it('should format MILLISEC', () => {
- expect(formatMeasure(0, 'MILLISEC')).toBe('0ms');
- expect(formatMeasure(1, 'MILLISEC')).toBe('1ms');
- expect(formatMeasure(173, 'MILLISEC')).toBe('173ms');
- expect(formatMeasure(3649, 'MILLISEC')).toBe('4s');
- expect(formatMeasure(893481, 'MILLISEC')).toBe('15min');
- expect(formatMeasure(17862325, 'MILLISEC')).toBe('298min');
- });
-
- it('should not format unknown type', () => {
- expect(formatMeasure('random value', 'RANDOM_TYPE')).toBe('random value');
- });
-
- it('should return null if value is empty string', () => {
- expect(formatMeasure('', MetricType.Percent)).toBe('');
- });
-
- it('should not fail with undefined', () => {
- expect(formatMeasure(undefined, MetricType.Integer)).toBe('');
- });
-});
-
describe('areCCTMeasuresComputed', () => {
it('returns true when measures include maintainability_,security_,reliability_issues', () => {
expect(
diff --git a/server/sonar-web/src/main/js/helpers/measures.ts b/server/sonar-web/src/main/js/helpers/measures.ts
index 1f5b075904f..2ac1984bf76 100644
--- a/server/sonar-web/src/main/js/helpers/measures.ts
+++ b/server/sonar-web/src/main/js/helpers/measures.ts
@@ -22,15 +22,13 @@ import {
QualityGateStatusCondition,
QualityGateStatusConditionEnhanced,
} from '../types/quality-gates';
-import { Dict, Measure, MeasureEnhanced, Metric } from '../types/types';
+import { Measure, MeasureEnhanced, Metric } from '../types/types';
import {
CCT_SOFTWARE_QUALITY_METRICS,
LEAK_CCT_SOFTWARE_QUALITY_METRICS,
LEAK_OLD_TAXONOMY_METRICS,
- ONE_SECOND,
} from './constants';
-import { translate, translateWithParameters } from './l10n';
-import { getCurrentLocale } from './l10nBundle';
+import { translate } from './l10n';
import { isDefined } from './types';
export const MEASURES_REDIRECTION: Partial<Record<MetricKey, MetricKey>> = {
@@ -137,24 +135,6 @@ export const getCCTMeasureValue = (key: string, value?: string) => {
return value;
};
-const HOURS_IN_DAY = 8;
-
-type Formatter = (value: string | number, options?: Dict<unknown>) => string;
-
-/**
- * Format a measure value for a given type
- * ! For Ratings, use formatRating instead
- */
-export function formatMeasure(
- value: string | number | undefined,
- type: string,
- options?: Dict<unknown>,
-): string {
- const formatter = getFormatter(type);
- // eslint-disable-next-line react-hooks/rules-of-hooks
- return useFormatter(value, formatter, options);
-}
-
type RatingValue = 'A' | 'B' | 'C' | 'D' | 'E';
const RATING_VALUES: RatingValue[] = ['A', 'B', 'C', 'D', 'E'];
export function formatRating(value: string | number | undefined): RatingValue | undefined {
@@ -184,266 +164,3 @@ export function getShortType(type: string): string {
}
return type;
}
-
-function useFormatter(
- value: string | number | undefined,
- formatter: Formatter,
- options?: Dict<unknown>,
-): string {
- return value !== undefined && value !== '' ? formatter(value, options) : '';
-}
-
-function getFormatter(type: string): Formatter {
- const FORMATTERS: Dict<Formatter> = {
- 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 numberFormatter(
- value: string | number,
- minimumFractionDigits = 0,
- maximumFractionDigits = minimumFractionDigits,
-) {
- const { format } = new Intl.NumberFormat(getCurrentLocale(), {
- minimumFractionDigits,
- maximumFractionDigits,
- });
- if (typeof value === 'string') {
- return format(parseFloat(value));
- }
- return format(value);
-}
-
-function noFormatter(value: string | number): string | number {
- return value;
-}
-
-function intFormatter(value: string | number): string {
- return numberFormatter(value);
-}
-
-const shortIntFormats = [
- { unit: 1e10, formatUnit: 1e9, fraction: 0, suffix: 'short_number_suffix.g' },
- { unit: 1e9, formatUnit: 1e9, fraction: 1, suffix: 'short_number_suffix.g' },
- { unit: 1e7, formatUnit: 1e6, fraction: 0, suffix: 'short_number_suffix.m' },
- { unit: 1e6, formatUnit: 1e6, fraction: 1, suffix: 'short_number_suffix.m' },
- { unit: 1e4, formatUnit: 1e3, fraction: 0, suffix: 'short_number_suffix.k' },
- { unit: 1e3, formatUnit: 1e3, fraction: 1, suffix: 'short_number_suffix.k' },
-];
-
-function shortIntFormatter(
- value: string | number,
- option?: { roundingFunc?: (x: number) => number },
-): string {
- const roundingFunc = option?.roundingFunc;
- if (typeof value === 'string') {
- value = parseFloat(value);
- }
- for (let i = 0; i < shortIntFormats.length; i++) {
- const { unit, formatUnit, fraction, suffix } = shortIntFormats[i];
- const nextFraction = unit / (shortIntFormats[i + 1] ? shortIntFormats[i + 1].unit / 10 : 1);
- const roundedValue = numberRound(value / unit, nextFraction, roundingFunc);
- if (roundedValue >= 1) {
- return (
- numberFormatter(
- numberRound(value / formatUnit, Math.pow(10, fraction), roundingFunc),
- 0,
- fraction,
- ) + translate(suffix)
- );
- }
- }
-
- return numberFormatter(value);
-}
-
-function numberRound(
- value: number,
- fraction: number = 1000,
- roundingFunc: (x: number) => number = Math.round,
-) {
- return roundingFunc(value * fraction) / fraction;
-}
-
-function floatFormatter(value: string | number): string {
- return numberFormatter(value, 1, 5);
-}
-
-function percentFormatter(
- value: string | number,
- { decimals, omitExtraDecimalZeros }: { decimals?: number; omitExtraDecimalZeros?: boolean } = {},
-): string {
- if (typeof value === 'string') {
- value = parseFloat(value);
- }
- if (value === 100) {
- return '100%';
- } else if (omitExtraDecimalZeros && decimals) {
- // If omitExtraDecimalZeros is true, all trailing decimal 0s will be removed,
- // except for the first decimal.
- // E.g. for decimals=3:
- // - omitExtraDecimalZeros: false, value: 45.450 => 45.450
- // - omitExtraDecimalZeros: true, value: 45.450 => 45.45
- // - omitExtraDecimalZeros: false, value: 85 => 85.000
- // - omitExtraDecimalZeros: true, value: 85 => 85.0
- return `${numberFormatter(value, 1, decimals)}%`;
- }
- return `${numberFormatter(value, decimals || 1)}%`;
-}
-
-function ratingFormatter(value: string | number): string {
- if (typeof value === 'string') {
- value = parseInt(value, 10);
- }
- return String.fromCharCode(97 + value - 1).toUpperCase();
-}
-
-function levelFormatter(value: string | number): string {
- if (typeof value === 'number') {
- value = value.toString();
- }
- const l10nKey = `metric.level.${value}`;
- const result = translate(l10nKey);
-
- // if couldn't translate, return the initial value
- return l10nKey !== result ? result : value;
-}
-
-function millisecondsFormatter(value: string | number): string {
- if (typeof value === 'string') {
- value = parseInt(value, 10);
- }
- const ONE_MINUTE = 60 * ONE_SECOND;
- if (value >= ONE_MINUTE) {
- const minutes = Math.round(value / ONE_MINUTE);
- return `${minutes}min`;
- } else if (value >= ONE_SECOND) {
- const seconds = Math.round(value / ONE_SECOND);
- return `${seconds}s`;
- }
- return `${value}ms`;
-}
-
-/*
- * Debt Formatters
- */
-
-function shouldDisplayDays(days: number): boolean {
- return days > 0;
-}
-
-function shouldDisplayDaysInShortFormat(days: number): boolean {
- return days > 0.9;
-}
-
-function shouldDisplayHours(days: number, hours: number): boolean {
- return hours > 0 && days < 10;
-}
-
-function shouldDisplayHoursInShortFormat(hours: number): boolean {
- return hours > 0.9;
-}
-
-function shouldDisplayMinutes(days: number, hours: number, minutes: number): boolean {
- return minutes > 0 && hours < 10 && days === 0;
-}
-
-function addSpaceIfNeeded(value: string): string {
- return value.length > 0 ? `${value} ` : value;
-}
-
-function formatDuration(isNegative: boolean, days: number, hours: number, minutes: number): string {
- let formatted = '';
- if (shouldDisplayDays(days)) {
- formatted += translateWithParameters('work_duration.x_days', isNegative ? -1 * days : days);
- }
- if (shouldDisplayHours(days, hours)) {
- formatted = addSpaceIfNeeded(formatted);
- formatted += translateWithParameters(
- 'work_duration.x_hours',
- isNegative && formatted.length === 0 ? -1 * hours : hours,
- );
- }
- if (shouldDisplayMinutes(days, hours, minutes)) {
- formatted = addSpaceIfNeeded(formatted);
- formatted += translateWithParameters(
- 'work_duration.x_minutes',
- isNegative && formatted.length === 0 ? -1 * minutes : minutes,
- );
- }
- return formatted;
-}
-
-function formatDurationShort(
- isNegative: boolean,
- days: number,
- hours: number,
- minutes: number,
-): string {
- if (shouldDisplayDaysInShortFormat(days)) {
- const roundedDays = Math.round(days);
- const formattedDays = formatMeasure(
- isNegative ? -1 * roundedDays : roundedDays,
- MetricType.ShortInteger,
- );
- return translateWithParameters('work_duration.x_days', formattedDays);
- }
-
- if (shouldDisplayHoursInShortFormat(hours)) {
- const roundedHours = Math.round(hours);
- const formattedHours = formatMeasure(
- isNegative ? -1 * roundedHours : roundedHours,
- MetricType.ShortInteger,
- );
- return translateWithParameters('work_duration.x_hours', formattedHours);
- }
-
- const formattedMinutes = formatMeasure(
- isNegative ? -1 * minutes : minutes,
- MetricType.ShortInteger,
- );
- return translateWithParameters('work_duration.x_minutes', formattedMinutes);
-}
-
-function durationFormatter(value: string | number): string {
- if (typeof value === 'string') {
- value = parseInt(value, 10);
- }
- if (value === 0) {
- return '0';
- }
- const hoursInDay = HOURS_IN_DAY;
- const isNegative = value < 0;
- const absValue = Math.abs(value);
- const days = Math.floor(absValue / hoursInDay / 60);
- let remainingValue = absValue - days * hoursInDay * 60;
- const hours = Math.floor(remainingValue / 60);
- remainingValue -= hours * 60;
- return formatDuration(isNegative, days, hours, remainingValue);
-}
-
-function shortDurationFormatter(value: string | number): string {
- if (typeof value === 'string') {
- value = parseInt(value, 10);
- }
- if (value === 0) {
- return '0';
- }
- const hoursInDay = HOURS_IN_DAY;
- const isNegative = value < 0;
- const absValue = Math.abs(value);
- const days = absValue / hoursInDay / 60;
- let remainingValue = absValue - Math.floor(days) * hoursInDay * 60;
- const hours = remainingValue / 60;
- remainingValue -= Math.floor(hours) * 60;
- return formatDurationShort(isNegative, days, hours, remainingValue);
-}
diff --git a/server/sonar-web/src/main/js/sonar-aligned/helpers/__tests__/measures-test.ts b/server/sonar-web/src/main/js/sonar-aligned/helpers/__tests__/measures-test.ts
new file mode 100644
index 00000000000..8680dbb6a23
--- /dev/null
+++ b/server/sonar-web/src/main/js/sonar-aligned/helpers/__tests__/measures-test.ts
@@ -0,0 +1,207 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { getMessages } from '../../../helpers/l10nBundle';
+import { MetricType } from '../../../types/metrics';
+import { Dict } from '../../../types/types';
+import { formatMeasure } from '../measures';
+
+const HOURS_IN_DAY = 8;
+const ONE_MINUTE = 1;
+const ONE_HOUR = ONE_MINUTE * 60;
+const ONE_DAY = HOURS_IN_DAY * ONE_HOUR;
+
+jest.unmock('../../../helpers/l10n');
+
+jest.mock('../../../helpers/l10nBundle', () => ({
+ getCurrentLocale: jest.fn().mockReturnValue('us'),
+ getMessages: jest.fn().mockReturnValue({}),
+}));
+
+const resetMessages = (messages: Dict<string>) =>
+ jest.mocked(getMessages).mockReturnValue(messages);
+
+beforeAll(() => {
+ resetMessages({
+ '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',
+ 'short_number_suffix.g': 'G',
+ 'short_number_suffix.k': 'k',
+ 'short_number_suffix.m': 'M',
+ });
+});
+
+describe('#formatMeasure()', () => {
+ it('should format INT', () => {
+ expect(formatMeasure(0, MetricType.Integer)).toBe('0');
+ expect(formatMeasure(1, MetricType.Integer)).toBe('1');
+ expect(formatMeasure(-5, MetricType.Integer)).toBe('-5');
+ expect(formatMeasure(999, MetricType.Integer)).toBe('999');
+ expect(formatMeasure(1000, MetricType.Integer)).toBe('1,000');
+ expect(formatMeasure(1529, MetricType.Integer)).toBe('1,529');
+ expect(formatMeasure(10000, MetricType.Integer)).toBe('10,000');
+ expect(formatMeasure(1234567890, MetricType.Integer)).toBe('1,234,567,890');
+ });
+
+ it('should format SHORT_INT', () => {
+ expect(formatMeasure(0, MetricType.ShortInteger)).toBe('0');
+ expect(formatMeasure(1, MetricType.ShortInteger)).toBe('1');
+ expect(formatMeasure(999, MetricType.ShortInteger)).toBe('999');
+ expect(formatMeasure(1000, MetricType.ShortInteger)).toBe('1k');
+ expect(formatMeasure(1529, MetricType.ShortInteger)).toBe('1.5k');
+ expect(formatMeasure(10000, MetricType.ShortInteger)).toBe('10k');
+ expect(formatMeasure(10678, MetricType.ShortInteger)).toBe('11k');
+ expect(formatMeasure(9467890, MetricType.ShortInteger)).toBe('9.5M');
+ expect(formatMeasure(994567890, MetricType.ShortInteger)).toBe('995M');
+ expect(formatMeasure(999000001, MetricType.ShortInteger)).toBe('999M');
+ expect(formatMeasure(999567890, MetricType.ShortInteger)).toBe('1G');
+ expect(formatMeasure(1234567890, MetricType.ShortInteger)).toBe('1.2G');
+ expect(formatMeasure(11234567890, MetricType.ShortInteger)).toBe('11G');
+ });
+
+ it('should format FLOAT', () => {
+ expect(formatMeasure(0, 'FLOAT')).toBe('0.0');
+ expect(formatMeasure(1, 'FLOAT')).toBe('1.0');
+ expect(formatMeasure(1.3, 'FLOAT')).toBe('1.3');
+ expect(formatMeasure(1.34, 'FLOAT')).toBe('1.34');
+ expect(formatMeasure(50.89, 'FLOAT')).toBe('50.89');
+ expect(formatMeasure(100, 'FLOAT')).toBe('100.0');
+ expect(formatMeasure(123.456, 'FLOAT')).toBe('123.456');
+ expect(formatMeasure(123456.7, 'FLOAT')).toBe('123,456.7');
+ expect(formatMeasure(1234567890, 'FLOAT')).toBe('1,234,567,890.0');
+ });
+
+ it('should respect FLOAT precision', () => {
+ expect(formatMeasure(0.1, 'FLOAT')).toBe('0.1');
+ expect(formatMeasure(0.12, 'FLOAT')).toBe('0.12');
+ expect(formatMeasure(0.12345, 'FLOAT')).toBe('0.12345');
+ expect(formatMeasure(0.123456, 'FLOAT')).toBe('0.12346');
+ });
+
+ it('should format PERCENT', () => {
+ expect(formatMeasure(0, MetricType.Percent)).toBe('0.0%');
+ expect(formatMeasure(1, MetricType.Percent)).toBe('1.0%');
+ expect(formatMeasure(1.3, MetricType.Percent)).toBe('1.3%');
+ expect(formatMeasure(1.34, MetricType.Percent)).toBe('1.3%');
+ expect(formatMeasure(50.89, MetricType.Percent)).toBe('50.9%');
+ expect(formatMeasure(100, MetricType.Percent)).toBe('100%');
+ expect(formatMeasure(50.89, MetricType.Percent, { decimals: 0 })).toBe('50.9%');
+ expect(formatMeasure(50.89, MetricType.Percent, { decimals: 1 })).toBe('50.9%');
+ expect(formatMeasure(50.89, MetricType.Percent, { decimals: 2 })).toBe('50.89%');
+ expect(formatMeasure(50.89, MetricType.Percent, { decimals: 3 })).toBe('50.890%');
+ expect(
+ formatMeasure(50, MetricType.Percent, { decimals: 0, omitExtraDecimalZeros: true }),
+ ).toBe('50.0%');
+ expect(
+ formatMeasure(50, MetricType.Percent, { decimals: 1, omitExtraDecimalZeros: true }),
+ ).toBe('50.0%');
+ expect(
+ formatMeasure(50, MetricType.Percent, { decimals: 3, omitExtraDecimalZeros: true }),
+ ).toBe('50.0%');
+ expect(
+ formatMeasure(50.89, MetricType.Percent, { decimals: 3, omitExtraDecimalZeros: true }),
+ ).toBe('50.89%');
+ });
+
+ it('should format WORK_DUR', () => {
+ expect(formatMeasure(0, 'WORK_DUR')).toBe('0');
+ expect(formatMeasure(5 * ONE_DAY, 'WORK_DUR')).toBe('5d');
+ expect(formatMeasure(2 * ONE_HOUR, 'WORK_DUR')).toBe('2h');
+ expect(formatMeasure(40 * ONE_MINUTE, 'WORK_DUR')).toBe('40min');
+ expect(formatMeasure(ONE_MINUTE, 'WORK_DUR')).toBe('1min');
+ expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR, 'WORK_DUR')).toBe('5d 2h');
+ expect(formatMeasure(2 * ONE_HOUR + ONE_MINUTE, 'WORK_DUR')).toBe('2h 1min');
+ expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'WORK_DUR')).toBe('5d 2h');
+ expect(formatMeasure(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'WORK_DUR')).toBe('15d');
+ expect(formatMeasure(-5 * ONE_DAY, 'WORK_DUR')).toBe('-5d');
+ expect(formatMeasure(-2 * ONE_HOUR, 'WORK_DUR')).toBe('-2h');
+ expect(formatMeasure(-1 * ONE_MINUTE, 'WORK_DUR')).toBe('-1min');
+ });
+
+ it('should format SHORT_WORK_DUR', () => {
+ expect(formatMeasure(0, MetricType.ShortWorkDuration)).toBe('0');
+ expect(formatMeasure(5 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('5d');
+ expect(formatMeasure(2 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('2h');
+ expect(formatMeasure(ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('1min');
+ expect(formatMeasure(40 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('40min');
+ expect(formatMeasure(58 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('1h');
+ expect(formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('5d');
+ expect(formatMeasure(2 * ONE_HOUR + ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('2h');
+ expect(formatMeasure(ONE_HOUR + 55 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('2h');
+ expect(formatMeasure(3 * ONE_DAY + 6 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('4d');
+ expect(formatMeasure(7 * ONE_HOUR + 59 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('1d');
+ expect(
+ formatMeasure(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, MetricType.ShortWorkDuration),
+ ).toBe('5d');
+ expect(
+ formatMeasure(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, MetricType.ShortWorkDuration),
+ ).toBe('15d');
+ expect(formatMeasure(7 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('7min');
+ expect(formatMeasure(-5 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('-5d');
+ expect(formatMeasure(-2 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe('-2h');
+ expect(formatMeasure(-1 * ONE_MINUTE, MetricType.ShortWorkDuration)).toBe('-1min');
+
+ expect(formatMeasure(1529 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('1.5kd');
+ expect(formatMeasure(1234567 * ONE_DAY, MetricType.ShortWorkDuration)).toBe('1.2Md');
+ expect(formatMeasure(12345670 * ONE_DAY + 4 * ONE_HOUR, MetricType.ShortWorkDuration)).toBe(
+ '12Md',
+ );
+ });
+
+ it('should format RATING', () => {
+ expect(formatMeasure(1, MetricType.Rating)).toBe('A');
+ expect(formatMeasure(2, MetricType.Rating)).toBe('B');
+ expect(formatMeasure(3, MetricType.Rating)).toBe('C');
+ expect(formatMeasure(4, MetricType.Rating)).toBe('D');
+ expect(formatMeasure(5, MetricType.Rating)).toBe('E');
+ });
+
+ it('should format LEVEL', () => {
+ expect(formatMeasure('ERROR', MetricType.Level)).toBe('Error');
+ expect(formatMeasure('WARN', MetricType.Level)).toBe('Warning');
+ expect(formatMeasure('OK', MetricType.Level)).toBe('Ok');
+ expect(formatMeasure('UNKNOWN', MetricType.Level)).toBe('UNKNOWN');
+ });
+
+ it('should format MILLISEC', () => {
+ expect(formatMeasure(0, 'MILLISEC')).toBe('0ms');
+ expect(formatMeasure(1, 'MILLISEC')).toBe('1ms');
+ expect(formatMeasure(173, 'MILLISEC')).toBe('173ms');
+ expect(formatMeasure(3649, 'MILLISEC')).toBe('4s');
+ expect(formatMeasure(893481, 'MILLISEC')).toBe('15min');
+ expect(formatMeasure(17862325, 'MILLISEC')).toBe('298min');
+ });
+
+ it('should not format unknown type', () => {
+ expect(formatMeasure('random value', 'RANDOM_TYPE')).toBe('random value');
+ });
+
+ it('should return null if value is empty string', () => {
+ expect(formatMeasure('', MetricType.Percent)).toBe('');
+ });
+
+ it('should not fail with undefined', () => {
+ expect(formatMeasure(undefined, MetricType.Integer)).toBe('');
+ });
+});
diff --git a/server/sonar-web/src/main/js/sonar-aligned/helpers/measures.ts b/server/sonar-web/src/main/js/sonar-aligned/helpers/measures.ts
new file mode 100644
index 00000000000..1969f3da1d1
--- /dev/null
+++ b/server/sonar-web/src/main/js/sonar-aligned/helpers/measures.ts
@@ -0,0 +1,310 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+import { ONE_SECOND } from '../../helpers/constants';
+import { translate, translateWithParameters } from '../../helpers/l10n';
+import { getCurrentLocale } from '../../helpers/l10nBundle';
+import { MetricType } from '../../types/metrics';
+
+import { Dict } from '../../types/types';
+
+const HOURS_IN_DAY = 8;
+
+type FormatterOption =
+ | { roundingFunc?: (x: number) => number }
+ | { decimals?: number; omitExtraDecimalZeros?: boolean };
+
+type Formatter = (value: string | number, options?: FormatterOption) => string;
+/**
+ * Format a measure value for a given type
+ * ! For Ratings, use formatRating instead
+ */
+
+export function formatMeasure(
+ value: string | number | undefined,
+ type: string,
+ options?: FormatterOption,
+): string {
+ const formatter = getFormatter(type);
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ return useFormatter(value, formatter, options);
+}
+
+function useFormatter(
+ value: string | number | undefined,
+ formatter: Formatter,
+ options?: FormatterOption,
+): string {
+ return value !== undefined && value !== '' ? formatter(value, options) : '';
+}
+
+function getFormatter(type: string): Formatter {
+ const FORMATTERS: Dict<Formatter> = {
+ 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 noFormatter(value: string | number): string | number {
+ return value;
+}
+
+function numberFormatter(
+ value: string | number,
+ minimumFractionDigits = 0,
+ maximumFractionDigits = minimumFractionDigits,
+) {
+ const { format } = new Intl.NumberFormat(getCurrentLocale(), {
+ minimumFractionDigits,
+ maximumFractionDigits,
+ });
+ if (typeof value === 'string') {
+ return format(parseFloat(value));
+ }
+ return format(value);
+}
+
+function intFormatter(value: string | number): string {
+ return numberFormatter(value);
+}
+
+function shortIntFormatter(
+ value: string | number,
+ option?: { roundingFunc?: (x: number) => number },
+): string {
+ const shortIntFormats = [
+ { unit: 10000000000, formatUnit: 1000000000, fraction: 0, suffix: 'short_number_suffix.g' },
+ { unit: 1000000000, formatUnit: 1000000000, fraction: 1, suffix: 'short_number_suffix.g' },
+ { unit: 10000000, formatUnit: 1000000, fraction: 0, suffix: 'short_number_suffix.m' },
+ { unit: 1000000, formatUnit: 1000000, fraction: 1, suffix: 'short_number_suffix.m' },
+ { unit: 10000, formatUnit: 1000, fraction: 0, suffix: 'short_number_suffix.k' },
+ { unit: 1000, formatUnit: 1000, fraction: 1, suffix: 'short_number_suffix.k' },
+ ];
+
+ const roundingFunc = option?.roundingFunc;
+ if (typeof value === 'string') {
+ value = parseFloat(value);
+ }
+ for (let i = 0; i < shortIntFormats.length; i++) {
+ const { unit, formatUnit, fraction, suffix } = shortIntFormats[i];
+ const nextFraction = unit / (shortIntFormats[i + 1] ? shortIntFormats[i + 1].unit / 10 : 1);
+ const roundedValue = numberRound(value / unit, nextFraction, roundingFunc);
+ if (roundedValue >= 1) {
+ return (
+ numberFormatter(
+ numberRound(value / formatUnit, Math.pow(10, fraction), roundingFunc),
+ 0,
+ fraction,
+ ) + translate(suffix)
+ );
+ }
+ }
+
+ return numberFormatter(value);
+}
+
+function numberRound(
+ value: number,
+ fraction = 1000,
+ roundingFunc: (x: number) => number = Math.round,
+) {
+ return roundingFunc(value * fraction) / fraction;
+}
+
+function floatFormatter(value: string | number): string {
+ return numberFormatter(value, 1, 5);
+}
+
+function percentFormatter(
+ value: string | number,
+ { decimals, omitExtraDecimalZeros }: { decimals?: number; omitExtraDecimalZeros?: boolean } = {},
+): string {
+ if (typeof value === 'string') {
+ value = parseFloat(value);
+ }
+ if (value === 100) {
+ return '100%';
+ } else if (omitExtraDecimalZeros && decimals) {
+ // If omitExtraDecimalZeros is true, all trailing decimal 0s will be removed,
+ // except for the first decimal.
+ // E.g. for decimals=3:
+ // - omitExtraDecimalZeros: false, value: 45.450 => 45.450
+ // - omitExtraDecimalZeros: true, value: 45.450 => 45.45
+ // - omitExtraDecimalZeros: false, value: 85 => 85.000
+ // - omitExtraDecimalZeros: true, value: 85 => 85.0
+ return `${numberFormatter(value, 1, decimals)}%`;
+ }
+ return `${numberFormatter(value, decimals || 1)}%`;
+}
+
+function ratingFormatter(value: string | number): string {
+ if (typeof value === 'string') {
+ value = parseInt(value, 10);
+ }
+ return String.fromCharCode(97 + value - 1).toUpperCase();
+}
+
+function levelFormatter(value: string | number): string {
+ if (typeof value === 'number') {
+ value = value.toString();
+ }
+ const l10nKey = `metric.level.${value}`;
+ const result = translate(l10nKey);
+
+ // if couldn't translate, return the initial value
+ return l10nKey !== result ? result : value;
+}
+
+function millisecondsFormatter(value: string | number): string {
+ if (typeof value === 'string') {
+ value = parseInt(value, 10);
+ }
+ const ONE_MINUTE = 60 * ONE_SECOND;
+ if (value >= ONE_MINUTE) {
+ const minutes = Math.round(value / ONE_MINUTE);
+ return `${minutes}min`;
+ } else if (value >= ONE_SECOND) {
+ const seconds = Math.round(value / ONE_SECOND);
+ return `${seconds}s`;
+ }
+ return `${value}ms`;
+}
+
+function formatDuration(isNegative: boolean, days: number, hours: number, minutes: number): string {
+ let formatted = '';
+ if (shouldDisplayDays(days)) {
+ formatted += translateWithParameters('work_duration.x_days', isNegative ? -1 * days : days);
+ }
+ if (shouldDisplayHours(days, hours)) {
+ formatted = addSpaceIfNeeded(formatted);
+ formatted += translateWithParameters(
+ 'work_duration.x_hours',
+ isNegative && formatted.length === 0 ? -1 * hours : hours,
+ );
+ }
+ if (shouldDisplayMinutes(days, hours, minutes)) {
+ formatted = addSpaceIfNeeded(formatted);
+ formatted += translateWithParameters(
+ 'work_duration.x_minutes',
+ isNegative && formatted.length === 0 ? -1 * minutes : minutes,
+ );
+ }
+ return formatted;
+}
+
+function formatDurationShort(
+ isNegative: boolean,
+ days: number,
+ hours: number,
+ minutes: number,
+): string {
+ if (shouldDisplayDaysInShortFormat(days)) {
+ const roundedDays = Math.round(days);
+ const formattedDays = formatMeasure(
+ isNegative ? -1 * roundedDays : roundedDays,
+ MetricType.ShortInteger,
+ );
+ return translateWithParameters('work_duration.x_days', formattedDays);
+ }
+
+ if (shouldDisplayHoursInShortFormat(hours)) {
+ const roundedHours = Math.round(hours);
+ const formattedHours = formatMeasure(
+ isNegative ? -1 * roundedHours : roundedHours,
+ MetricType.ShortInteger,
+ );
+ return translateWithParameters('work_duration.x_hours', formattedHours);
+ }
+
+ const formattedMinutes = formatMeasure(
+ isNegative ? -1 * minutes : minutes,
+ MetricType.ShortInteger,
+ );
+ return translateWithParameters('work_duration.x_minutes', formattedMinutes);
+}
+
+function durationFormatter(value: string | number): string {
+ if (typeof value === 'string') {
+ value = parseInt(value, 10);
+ }
+ if (value === 0) {
+ return '0';
+ }
+ const hoursInDay = HOURS_IN_DAY;
+ const isNegative = value < 0;
+ const absValue = Math.abs(value);
+ const days = Math.floor(absValue / hoursInDay / 60);
+ let remainingValue = absValue - days * hoursInDay * 60;
+ const hours = Math.floor(remainingValue / 60);
+ remainingValue -= hours * 60;
+ return formatDuration(isNegative, days, hours, remainingValue);
+}
+
+function shortDurationFormatter(value: string | number): string {
+ if (typeof value === 'string') {
+ value = parseInt(value, 10);
+ }
+ if (value === 0) {
+ return '0';
+ }
+ const hoursInDay = HOURS_IN_DAY;
+ const isNegative = value < 0;
+ const absValue = Math.abs(value);
+ const days = absValue / hoursInDay / 60;
+ let remainingValue = absValue - Math.floor(days) * hoursInDay * 60;
+ const hours = remainingValue / 60;
+ remainingValue -= Math.floor(hours) * 60;
+ return formatDurationShort(isNegative, days, hours, remainingValue);
+}
+
+/*
+ * Debt Formatters
+ */
+function shouldDisplayDays(days: number): boolean {
+ return days > 0;
+}
+
+function shouldDisplayDaysInShortFormat(days: number): boolean {
+ return days > 0.9;
+}
+
+function shouldDisplayHours(days: number, hours: number): boolean {
+ return hours > 0 && days < 10;
+}
+
+function shouldDisplayHoursInShortFormat(hours: number): boolean {
+ return hours > 0.9;
+}
+
+function shouldDisplayMinutes(days: number, hours: number, minutes: number): boolean {
+ return minutes > 0 && hours < 10 && days === 0;
+}
+
+function addSpaceIfNeeded(value: string): string {
+ return value.length > 0 ? `${value} ` : value;
+}