From 57b2e33c8c321d3297631de46e1fda374e3b17d8 Mon Sep 17 00:00:00 2001 From: Viktor Vorona Date: Tue, 23 Apr 2024 16:41:37 +0200 Subject: [PATCH] SONAR-22049 Align formatMeasure --- .../branch-like/QualityGateStatus.tsx | 2 +- .../js/apps/account/projects/ProjectCard.tsx | 2 +- .../components/StatPendingTime.tsx | 2 +- .../apps/code/components/ComponentMeasure.tsx | 2 +- .../components/BulkChangeModal.tsx | 2 +- .../js/apps/coding-rules/components/Facet.tsx | 2 +- .../components/RuleDetailsIssues.tsx | 2 +- .../components/MeasureHeader.tsx | 3 +- .../drilldown/BubbleChartView.tsx | 3 +- .../drilldown/ColorRatingsLegend.tsx | 2 +- .../drilldown/FilesView.tsx | 3 +- .../drilldown/MeasureCell.tsx | 3 +- .../drilldown/TreeMapView.tsx | 3 +- .../sidebar/SubnavigationMeasureValue.tsx | 3 +- .../js/apps/issues/components/TotalEffort.tsx | 2 +- .../apps/issues/sidebar/CreationDateFacet.tsx | 2 +- .../js/apps/issues/sidebar/ListStyleFacet.tsx | 2 +- .../issues/sidebar/ListStyleFacetFooter.tsx | 2 +- .../src/main/js/apps/issues/utils.ts | 2 +- .../overview/branches/AnalysisVariations.tsx | 2 +- .../overview/branches/BranchMetaTopBar.tsx | 3 +- .../branches/NewCodeMeasuresPanel.tsx | 3 +- .../branches/OverallCodeMeasuresPanel.tsx | 3 +- .../branches/QualityGateCondition.tsx | 5 +- .../QualityGateSimplifiedCondition.tsx | 3 +- .../SoftwareImpactMeasureBreakdownCard.tsx | 2 +- .../branches/SoftwareImpactMeasureCard.tsx | 2 +- .../components/MeasuresCardNumber.tsx | 2 +- .../components/MeasuresCardPercent.tsx | 3 +- .../BranchQualityGateConditions.tsx | 3 +- .../pullRequests/IssueMeasuresCard.tsx | 3 +- .../pullRequests/PullRequestMetaTopBar.tsx | 3 +- .../src/main/js/apps/overview/utils.tsx | 2 +- .../LifetimeInformationRenderer.tsx | 2 +- .../about/components/MetaSize.tsx | 3 +- .../components/project-card/ProjectCard.tsx | 2 +- .../js/apps/projects/filters/RatingFacet.tsx | 2 +- .../projects/filters/SecurityReviewFilter.tsx | 2 +- .../components/CaycCondition.tsx | 2 +- .../components/ConditionValue.tsx | 2 +- .../components/ConditionValueDescription.tsx | 2 +- .../details/ProfileRulesRow.tsx | 2 +- .../quality-profiles/home/EvolutionRules.tsx | 2 +- .../components/StatusUpdateSuccessModal.tsx | 2 +- .../src/main/js/apps/system/utils.ts | 2 +- .../apps/webhooks/components/DeliveryItem.tsx | 2 +- .../SourceViewer/SourceViewerHeader.tsx | 6 +- .../activity-graph/DataTableModal.tsx | 2 +- .../activity-graph/GraphHistory.tsx | 3 +- .../GraphsTooltipsContentCoverage.tsx | 2 +- .../GraphsTooltipsContentDuplication.tsx | 2 +- .../js/components/charts/ColorBoxLegend.tsx | 2 +- .../charts/LanguageDistribution.tsx | 2 +- .../main/js/components/common/PageCounter.tsx | 2 +- .../js/components/controls/ListFooter.tsx | 2 +- .../issue/components/IssueChangelogDiff.tsx | 2 +- .../__tests__/IssueChangelogDiff-test.tsx | 2 +- .../main/js/components/measure/Measure.tsx | 4 +- .../components/measure/MeasureIndicator.tsx | 4 +- .../measure/RatingTooltipContent.tsx | 3 +- .../main/js/components/ui/FilesCounter.tsx | 2 +- .../src/main/js/components/ui/Rating.tsx | 2 +- .../js/helpers/__tests__/measures-test.ts | 189 +---------- .../sonar-web/src/main/js/helpers/measures.ts | 287 +--------------- .../helpers/__tests__/measures-test.ts | 207 ++++++++++++ .../main/js/sonar-aligned/helpers/measures.ts | 310 ++++++++++++++++++ 66 files changed, 604 insertions(+), 541 deletions(-) create mode 100644 server/sonar-web/src/main/js/sonar-aligned/helpers/__tests__/measures-test.ts create mode 100644 server/sonar-web/src/main/js/sonar-aligned/helpers/measures.ts 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 { 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) => - (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> = { @@ -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) => 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, -): 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, -): string { - return value !== undefined && value !== '' ? formatter(value, options) : ''; -} - -function getFormatter(type: string): Formatter { - const FORMATTERS: Dict = { - 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) => + 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 = { + 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; +} -- 2.39.5