--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 {
+ mockMeasureEnhanced,
+ mockMetric,
+ mockQualityGateStatusCondition
+} from '../../../helpers/testMocks';
+import { getThreshold } from '../utils';
+
+describe('getThreshold', () => {
+ it('return undefined if condition is not found', () => {
+ expect(getThreshold([], '')).toBeUndefined();
+ expect(getThreshold([mockMeasure()], '')).toBeUndefined();
+ expect(
+ getThreshold(
+ [
+ {
+ metric: mockMetric({ key: 'quality_gate_details' }),
+ value: 'badly typed json should fail'
+ }
+ ],
+ ''
+ )
+ ).toBeUndefined();
+ });
+
+ it('should return the threshold for the right metric', () => {
+ expect(getThreshold([mockMeasure()], 'new_coverage')).toBe(85);
+ expect(getThreshold([mockMeasure()], 'new_duplicated_lines_density')).toBe(5);
+ });
+});
+
+function mockMeasure() {
+ return mockMeasureEnhanced({
+ metric: mockMetric({ key: 'quality_gate_details' }),
+ value: JSON.stringify({
+ conditions: [
+ mockQualityGateStatusCondition({
+ metric: 'new_coverage',
+ level: 'ERROR',
+ error: '85'
+ }),
+ mockQualityGateStatusCondition({
+ metric: 'new_duplicated_lines_density',
+ level: 'WARNING',
+ warning: '5'
+ })
+ ]
+ })
+ });
+}
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { formatMeasure } from 'sonar-ui-common/helpers/measures';
+import {
+ formatMeasure,
+ getMinDecimalsCountToBeDistinctFromThreshold
+} from 'sonar-ui-common/helpers/measures';
import DocTooltip from '../../../components/docs/DocTooltip';
import DrilldownLink from '../../../components/shared/DrilldownLink';
import CoverageRating from '../../../components/ui/CoverageRating';
import { getPeriodValue } from '../../../helpers/measures';
-import { getMetricName } from '../utils';
+import { getMetricName, getThreshold } from '../utils';
import enhance, { ComposedProps } from './enhance';
export class Coverage extends React.PureComponent<ComposedProps> {
component={component.key}
metric={newCoverageMeasure.metric.key}>
<span className="js-overview-main-new-coverage">
- {formatMeasure(newCoverageValue, 'PERCENT')}
+ {formatMeasure(newCoverageValue, 'PERCENT', {
+ decimals: getMinDecimalsCountToBeDistinctFromThreshold(
+ parseFloat(newCoverageValue),
+ getThreshold(measures, 'new_coverage')
+ )
+ })}
</span>
</DrilldownLink>
</div>
import * as React from 'react';
import DuplicationsRating from 'sonar-ui-common/components/ui/DuplicationsRating';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { formatMeasure } from 'sonar-ui-common/helpers/measures';
+import {
+ formatMeasure,
+ getMinDecimalsCountToBeDistinctFromThreshold
+} from 'sonar-ui-common/helpers/measures';
import DocTooltip from '../../../components/docs/DocTooltip';
import DrilldownLink from '../../../components/shared/DrilldownLink';
import { getPeriodValue } from '../../../helpers/measures';
-import { getMetricName } from '../utils';
+import { getMetricName, getThreshold } from '../utils';
import enhance, { ComposedProps } from './enhance';
export class Duplications extends React.PureComponent<ComposedProps> {
component={component.key}
metric={newDuplicationsMeasure.metric.key}>
<span className="js-overview-main-new-duplications">
- {formatMeasure(newDuplicationsValue, 'PERCENT')}
+ {formatMeasure(newDuplicationsValue, 'PERCENT', {
+ decimals: getMinDecimalsCountToBeDistinctFromThreshold(
+ parseFloat(newDuplicationsValue),
+ getThreshold(measures, 'new_duplicated_lines_density')
+ )
+ })}
</span>
</DrilldownLink>
</div>
import { Link } from 'react-router';
import IssueTypeIcon from 'sonar-ui-common/components/icons/IssueTypeIcon';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { formatMeasure } from 'sonar-ui-common/helpers/measures';
+import {
+ formatMeasure,
+ getMinDecimalsCountToBeDistinctFromThreshold
+} from 'sonar-ui-common/helpers/measures';
import Measure from '../../../components/measure/Measure';
import DrilldownLink from '../../../components/shared/DrilldownLink';
import { getBranchLikeQuery, isPullRequest, isShortLivingBranch } from '../../../helpers/branches';
}
export default class QualityGateCondition extends React.PureComponent<Props> {
- getDecimalsNumber(threshold: number, value: number) {
- const delta = Math.abs(threshold - value);
- if (delta < 0.1 && delta > 0) {
- const match = delta.toFixed(20).match('[^0.]');
- return match && match.index ? match.index - 1 : undefined;
- } else {
- return undefined;
- }
- }
-
getIssuesUrl = (sinceLeakPeriod: boolean, customQuery: T.Dict<string>) => {
const query: T.Dict<string | undefined> = {
resolved: 'false',
if (metric.type === 'RATING') {
operator = translate('quality_gates.operator', condition.op, 'rating');
} else if (metric.type === 'PERCENT') {
- decimals = this.getDecimalsNumber(parseFloat(threshold), parseFloat(actual));
+ decimals = getMinDecimalsCountToBeDistinctFromThreshold(
+ parseFloat(actual),
+ parseFloat(threshold)
+ );
}
return this.wrapWithLink(
).toMatchSnapshot();
});
-it('should be able to correctly decide how much decimals to show', () => {
- const condition = mockRatingCondition('new_maintainability_rating');
- const instance = shallow(
- <QualityGateCondition component={{ key: 'abcd-key' }} condition={condition} />
- ).instance() as QualityGateCondition;
- expect(instance.getDecimalsNumber(85, 80)).toBe(undefined);
- expect(instance.getDecimalsNumber(85, 85)).toBe(undefined);
- expect(instance.getDecimalsNumber(85, 85.01)).toBe(2);
- expect(instance.getDecimalsNumber(85, 84.95)).toBe(2);
- expect(instance.getDecimalsNumber(85, 84.999999999999554)).toBe('9999999999995'.length);
- expect(instance.getDecimalsNumber(85, 85.0000000000000954)).toBe('00000000000009'.length);
- expect(instance.getDecimalsNumber(85, 85.00000000000000009)).toBe(undefined);
-});
-
it('should work with branch', () => {
const condition = mockRatingCondition('new_maintainability_rating');
expect(
export function getRatingName(type: IssueType) {
return translate('metric_domain', ISSUETYPE_MAP[type].ratingName);
}
+
+/*
+ * Extract a specific metric's threshold from the quality gate details
+ */
+export function getThreshold(measures: T.MeasureEnhanced[], metricKey: string): number | undefined {
+ const detailsMeasure = measures.find(measure => measure.metric.key === 'quality_gate_details');
+ if (detailsMeasure && detailsMeasure.value) {
+ const details = safeParse(detailsMeasure.value);
+ const conditions: T.QualityGateStatusConditionEnhanced[] = details.conditions || [];
+
+ const condition = conditions.find(c => c.metric === metricKey);
+ if (condition) {
+ return parseFloat((condition.level === 'ERROR'
+ ? condition.error
+ : condition.warning) as string);
+ }
+ }
+ return undefined;
+}
+
+function safeParse(json: string) {
+ try {
+ return JSON.parse(json);
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error(e);
+ return {};
+ }
+}