You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MeasuresCardPercent.tsx 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import classNames from 'classnames';
  21. import {
  22. ContentLink,
  23. CoverageIndicator,
  24. DuplicationsIndicator,
  25. LightLabel,
  26. TextError,
  27. } from 'design-system';
  28. import * as React from 'react';
  29. import { FormattedMessage, useIntl } from 'react-intl';
  30. import { To } from 'react-router-dom';
  31. import { duplicationRatingConverter, getLeakValue } from '../../../components/measure/utils';
  32. import { translate, translateWithParameters } from '../../../helpers/l10n';
  33. import { findMeasure, formatMeasure, localizeMetric } from '../../../helpers/measures';
  34. import { getComponentDrilldownUrl } from '../../../helpers/urls';
  35. import { BranchLike } from '../../../types/branch-like';
  36. import { MetricKey, MetricType } from '../../../types/metrics';
  37. import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
  38. import { MeasureEnhanced } from '../../../types/types';
  39. import {
  40. MeasurementType,
  41. Status,
  42. getConditionRequiredLabel,
  43. getMeasurementMetricKey,
  44. } from '../utils';
  45. import MeasuresCard from './MeasuresCard';
  46. interface Props {
  47. componentKey: string;
  48. branchLike?: BranchLike;
  49. measurementType: MeasurementType;
  50. label: string;
  51. url: To;
  52. measures: MeasureEnhanced[];
  53. conditions: QualityGateStatusConditionEnhanced[];
  54. conditionMetric: MetricKey;
  55. linesMetric: MetricKey;
  56. useDiffMetric?: boolean;
  57. showRequired?: boolean;
  58. }
  59. export default function MeasuresCardPercent(
  60. props: React.PropsWithChildren<Props & React.HTMLAttributes<HTMLDivElement>>,
  61. ) {
  62. const {
  63. componentKey,
  64. branchLike,
  65. measurementType,
  66. label,
  67. url,
  68. measures,
  69. conditions,
  70. conditionMetric,
  71. linesMetric,
  72. useDiffMetric = false,
  73. showRequired = false,
  74. } = props;
  75. const intl = useIntl();
  76. const metricKey = getMeasurementMetricKey(measurementType, useDiffMetric);
  77. const value = useDiffMetric
  78. ? getLeakValue(findMeasure(measures, metricKey))
  79. : findMeasure(measures, metricKey)?.value;
  80. const linesValue = useDiffMetric
  81. ? getLeakValue(findMeasure(measures, linesMetric))
  82. : findMeasure(measures, linesMetric)?.value;
  83. const linesLabel = `overview.${metricKey}.on_x_new_lines`;
  84. const linesUrl = getComponentDrilldownUrl({
  85. componentKey,
  86. metric: linesMetric,
  87. branchLike,
  88. listView: true,
  89. });
  90. const condition = conditions.find((c) => c.metric === conditionMetric);
  91. const conditionFailed = condition?.level === Status.ERROR;
  92. const shouldRenderRequiredLabel = showRequired && condition;
  93. return (
  94. <MeasuresCard
  95. value={formatMeasure(value, MetricType.Percent)}
  96. metric={metricKey}
  97. url={url}
  98. label={label}
  99. failed={conditionFailed}
  100. icon={renderIcon(measurementType, value)}
  101. >
  102. {shouldRenderRequiredLabel && (
  103. <span className="sw-body-xs sw-mt-3">
  104. {conditionFailed ? (
  105. <TextError
  106. className="sw-font-regular sw-inline"
  107. text={getConditionRequiredLabel(condition, intl, true)}
  108. />
  109. ) : (
  110. <LightLabel>{getConditionRequiredLabel(condition, intl)}</LightLabel>
  111. )}
  112. </span>
  113. )}
  114. <div
  115. className={classNames('sw-flex sw-body-xs sw-justify-between sw-items-center', {
  116. 'sw-mt-1': shouldRenderRequiredLabel,
  117. 'sw-mt-3': !shouldRenderRequiredLabel,
  118. })}
  119. >
  120. <LightLabel className="sw-flex sw-gap-1">
  121. <FormattedMessage
  122. defaultMessage={translate(linesLabel)}
  123. id={linesLabel}
  124. values={{
  125. link: (
  126. <ContentLink
  127. aria-label={translateWithParameters(
  128. 'overview.see_more_details_on_x_y',
  129. linesValue ?? '0',
  130. localizeMetric(linesMetric),
  131. )}
  132. className="sw-body-sm-highlight sw--mt-[3px]"
  133. to={linesUrl}
  134. >
  135. {formatMeasure(linesValue ?? '0', MetricType.ShortInteger)}
  136. </ContentLink>
  137. ),
  138. }}
  139. />
  140. </LightLabel>
  141. </div>
  142. </MeasuresCard>
  143. );
  144. }
  145. function renderIcon(type: MeasurementType, value?: string) {
  146. if (type === MeasurementType.Coverage) {
  147. return <CoverageIndicator value={value} size="md" />;
  148. }
  149. const rating = duplicationRatingConverter(Number(value));
  150. return <DuplicationsIndicator rating={rating} size="md" />;
  151. }