Browse Source

SONAR-20742 Implement quality gate status

tags/10.3.0.82913
stanislavh 8 months ago
parent
commit
653eee5044

+ 14
- 1
server/sonar-web/__mocks__/react-intl.tsx View File

@@ -17,12 +17,25 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { isObject, some } from 'lodash';
import * as React from 'react';

module.exports = {
...jest.requireActual('react-intl'),
useIntl: () => ({
formatMessage: ({ id }, values = {}) => [id, ...Object.values(values)].join('.'),
formatMessage: ({ id }, values = {}) => {
if (some(values, isObject)) {
return (
<>
{id}
{Object.entries(values).map(([key, value]) => (
<React.Fragment key={key}>{value}</React.Fragment>
))}
</>
);
}
return [id, ...Object.values(values)].join('.');
},
}),
FormattedMessage: ({ id, values }: { id: string; values?: { [x: string]: React.ReactNode } }) => {
return (

+ 1
- 1
server/sonar-web/design-system/src/components/__tests__/layouts-test.tsx View File

@@ -26,7 +26,7 @@ describe('CenteredLayout', () => {

expect(screen.getByText('content')).toHaveStyle({
'min-width': '1280px',
'max-width': '1400px',
'max-width': '1280px',
});
});
});

+ 1
- 1
server/sonar-web/design-system/src/helpers/constants.ts View File

@@ -50,7 +50,7 @@ export const INPUT_SIZES = {
};

export const LAYOUT_VIEWPORT_MIN_WIDTH = 1280;
export const LAYOUT_VIEWPORT_MAX_WIDTH = 1400;
export const LAYOUT_VIEWPORT_MAX_WIDTH = 1280;
export const LAYOUT_VIEWPORT_MAX_WIDTH_LARGE = 1680;
export const LAYOUT_MAIN_CONTENT_GUTTER = 60;
export const LAYOUT_SIDEBAR_WIDTH = 240;

+ 87
- 0
server/sonar-web/src/main/js/apps/overview/components/BranchQualityGate.tsx View File

@@ -0,0 +1,87 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { HelperHintIcon, LightPrimary, QualityGateIndicator, TextMuted } from 'design-system';
import React from 'react';
import { useIntl } from 'react-intl';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import { BranchLike } from '../../../types/branch-like';
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
import { Component, Status } from '../../../types/types';
import BranchQualityGateConditions from './BranchQualityGateConditions';

interface Props {
status: Status;
branchLike?: BranchLike;
component: Pick<Component, 'key'>;
failedConditions: QualityGateStatusConditionEnhanced[];
}

export default function BranchQualityGate(props: Readonly<Props>) {
const { status, branchLike, component, failedConditions } = props;

return (
<>
<BranchQGStatus status={status} />
<BranchQualityGateConditions
branchLike={branchLike}
component={component}
failedConditions={failedConditions}
/>
</>
);
}

function BranchQGStatus({ status }: Readonly<Pick<Props, 'status'>>) {
const intl = useIntl();

return (
<div className="sw-flex sw-items-center sw-mb-5">
<QualityGateIndicator
status={status}
className="sw-mr-2"
size="xl"
ariaLabel={intl.formatMessage(
{ id: 'overview.quality_gate_x' },
{ '0': intl.formatMessage({ id: `overview.gate.${status}` }) },
)}
/>
<div className="sw-flex sw-flex-col sw-justify-around">
<div className="sw-flex sw-items-center">
<TextMuted
className="sw-body-sm"
text={intl.formatMessage({ id: 'overview.quality_gate' })}
/>
<HelpTooltip
className="sw-ml-2"
overlay={intl.formatMessage({ id: 'overview.quality_gate.help' })}
>
<HelperHintIcon aria-label="help-tooltip" />
</HelpTooltip>
</div>
<div>
<LightPrimary as="h1" className="sw-heading-xl">
{intl.formatMessage({ id: `metric.level.${status}` })}
</LightPrimary>
</div>
</div>
</div>
);
}

+ 197
- 0
server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx View File

@@ -0,0 +1,197 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { ChevronRightIcon, DangerButtonSecondary } from 'design-system';
import React from 'react';
import { useIntl } from 'react-intl';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { getLocalizedMetricName } from '../../../helpers/l10n';
import { formatMeasure, getShortType, isDiffMetric } from '../../../helpers/measures';
import {
getComponentDrilldownUrl,
getComponentIssuesUrl,
getComponentSecurityHotspotsUrl,
} from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like';
import { IssueType } from '../../../types/issues';
import { MetricKey, MetricType } from '../../../types/metrics';
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
import { Component } from '../../../types/types';
import {
METRICS_REPORTED_IN_OVERVIEW_CARDS,
RATING_METRICS_MAPPING,
RATING_TO_SEVERITIES_MAPPING,
} from '../utils';

interface Props {
branchLike?: BranchLike;
component: Pick<Component, 'key'>;
failedConditions: QualityGateStatusConditionEnhanced[];
}

export default function BranchQualityGateConditions(props: Readonly<Props>) {
const { branchLike, component, failedConditions } = props;

const filteredFailedConditions = failedConditions.filter(
(condition) => !METRICS_REPORTED_IN_OVERVIEW_CARDS.includes(condition.metric as MetricKey),
);

return (
<ul className="sw-flex sw-items-center sw-gap-2 sw-flex-wrap">
{filteredFailedConditions.map((condition) => (
<li key={condition.metric}>
<FailedQGCondition branchLike={branchLike} component={component} condition={condition} />
</li>
))}
</ul>
);
}

function FailedQGCondition(
props: Readonly<
Pick<Props, 'branchLike' | 'component'> & { condition: QualityGateStatusConditionEnhanced }
>,
) {
const { branchLike, component, condition } = props;
const url = getQGConditionUrl(component.key, condition, branchLike);

return (
<DangerButtonSecondary className="sw-px-2 sw-py-1 sw-rounded-1/2 sw-body-sm" to={url}>
<FailedMetric condition={condition} />
<ChevronRightIcon className="sw-ml-1" />
</DangerButtonSecondary>
);
}

interface FailedMetricProps {
condition: QualityGateStatusConditionEnhanced;
}

export function FailedMetric(props: Readonly<FailedMetricProps>) {
const {
condition: {
measure: { metric },
},
} = props;

if (metric.type === MetricType.Rating) {
return <FailedRatingMetric {...props} />;
}

return <FailedGeneralMetric {...props} />;
}

function FailedRatingMetric({ condition }: Readonly<FailedMetricProps>) {
const {
error,
measure: {
metric: { type, domain },
},
} = condition;
const intl = useIntl();

return (
<>
{intl.formatMessage(
{ id: 'overview.failed_condition.x_required' },
{
metric: `${intl.formatMessage({
id: `metric_domain.${domain}`,
})} ${intl.formatMessage({ id: 'metric.type.RATING' }).toLowerCase()}`,
threshold: (
<strong className="sw-body-sm-highlight sw-ml-1">{formatMeasure(error, type)}</strong>
),
},
)}
</>
);
}

function FailedGeneralMetric({ condition }: Readonly<FailedMetricProps>) {
const {
error,
measure: { metric },
} = condition;
const intl = useIntl();
const measureFormattingOptions = { decimals: 2, omitExtraDecimalZeros: true };

return (
<>
{intl.formatMessage(
{ id: 'overview.failed_condition.x_required' },
{
metric: (
<>
<strong className="sw-body-sm-highlight sw-mr-1">
{formatMeasure(
condition.actual,
getShortType(metric.type),
measureFormattingOptions,
)}
</strong>
{getLocalizedMetricName(metric, true)}
</>
),
threshold: (
<strong className="sw-body-sm-highlight sw-ml-1">
{condition.op === 'GT' ? <>&le;</> : <>&ge;</>}{' '}
{formatMeasure(error, getShortType(metric.type), measureFormattingOptions)}
</strong>
),
},
)}
</>
);
}

function getQGConditionUrl(
componentKey: string,
condition: QualityGateStatusConditionEnhanced,
branchLike?: BranchLike,
) {
const { metric } = condition;
const sinceLeakPeriod = isDiffMetric(metric);
const issueType = RATING_METRICS_MAPPING[metric];

if (issueType) {
if (issueType === IssueType.SecurityHotspot) {
return getComponentSecurityHotspotsUrl(componentKey, {
...getBranchLikeQuery(branchLike),
...(sinceLeakPeriod ? { sinceLeakPeriod: 'true' } : {}),
});
}
return getComponentIssuesUrl(componentKey, {
resolved: 'false',
types: issueType,
...getBranchLikeQuery(branchLike),
...(sinceLeakPeriod ? { sinceLeakPeriod: 'true' } : {}),
...(issueType !== IssueType.CodeSmell
? { severities: RATING_TO_SEVERITIES_MAPPING[Number(condition.error) - 1] }
: {}),
});
}

return getComponentDrilldownUrl({
componentKey,
metric,
branchLike,
listView: true,
});
}

+ 1
- 7
server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx View File

@@ -36,6 +36,7 @@ import { IssueType } from '../../../types/issues';
import { MetricKey, MetricType } from '../../../types/metrics';
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
import { Component, Dict } from '../../../types/types';
import { RATING_TO_SEVERITIES_MAPPING } from '../utils';

interface Props {
branchLike?: BranchLike;
@@ -71,13 +72,6 @@ export default class QualityGateCondition extends React.PureComponent<Props> {
}

getUrlForBugsOrVulnerabilities(type: string, inNewCodePeriod: boolean) {
const RATING_TO_SEVERITIES_MAPPING = [
'BLOCKER,CRITICAL,MAJOR,MINOR',
'BLOCKER,CRITICAL,MAJOR',
'BLOCKER,CRITICAL',
'BLOCKER',
];

const { condition } = this.props;
const threshold = condition.level === 'ERROR' ? condition.error : condition.warning;


+ 136
- 0
server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx View File

@@ -0,0 +1,136 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 * as React from 'react';
import { mockPullRequest } from '../../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../../helpers/mocks/component';
import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates';
import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { byLabelText, byRole } from '../../../../helpers/testSelector';
import { MetricKey, MetricType } from '../../../../types/metrics';
import { FCProps } from '../../../../types/misc';
import { Status } from '../../utils';
import BranchQualityGate from '../BranchQualityGate';

it('renders failed QG', () => {
renderBranchQualityGate();

// Maintainability rating condition
expect(
byRole('link', {
name: 'overview.failed_condition.x_requiredmetric_domain.Maintainability metric.type.rating A',
}).get(),
).toBeInTheDocument();

// Security Hotspots rating condition
expect(
byRole('link', {
name: 'overview.failed_condition.x_requiredmetric_domain.Security Review metric.type.rating A',
}).get(),
).toBeInTheDocument();

// New code smells
expect(
byRole('link', {
name: 'overview.failed_condition.x_required 5 Code Smells ≤ 1',
}).get(),
).toBeInTheDocument();

// Conditions to cover
expect(
byRole('link', {
name: 'overview.failed_condition.x_required 5 Conditions to cover ≥ 10',
}).get(),
).toBeInTheDocument();

expect(byLabelText('overview.quality_gate_x.overview.gate.ERROR').get()).toBeInTheDocument();
});

it('renders passed QG', () => {
renderBranchQualityGate({ failedConditions: [], status: Status.OK });

expect(byLabelText('overview.quality_gate_x.overview.gate.OK').get()).toBeInTheDocument();
expect(byRole('link').query()).not.toBeInTheDocument();
});

function renderBranchQualityGate(props: Partial<FCProps<typeof BranchQualityGate>> = {}) {
return renderComponent(
<BranchQualityGate
status={Status.ERROR}
branchLike={mockPullRequest()}
component={mockComponent()}
failedConditions={[
mockQualityGateStatusConditionEnhanced({
actual: '5.0',
error: '1.0',
metric: MetricKey.new_maintainability_rating,
measure: mockMeasureEnhanced({
metric: mockMetric({
domain: 'Maintainability',
key: MetricKey.new_maintainability_rating,
name: 'Maintainability rating',
type: MetricType.Rating,
}),
}),
}),
mockQualityGateStatusConditionEnhanced({
actual: '5.0',
error: '1.0',
metric: MetricKey.new_security_review_rating,
measure: mockMeasureEnhanced({
metric: mockMetric({
domain: 'Security Review',
key: MetricKey.new_security_review_rating,
name: 'Security Review Rating',
type: MetricType.Rating,
}),
}),
}),
mockQualityGateStatusConditionEnhanced({
actual: '5',
error: '1',
metric: MetricKey.new_code_smells,
measure: mockMeasureEnhanced({
metric: mockMetric({
domain: 'Maintainability',
key: MetricKey.new_code_smells,
name: 'Code Smells',
type: MetricType.ShortInteger,
}),
}),
}),
mockQualityGateStatusConditionEnhanced({
actual: '5',
error: '10',
op: 'up',
metric: MetricKey.conditions_to_cover,
measure: mockMeasureEnhanced({
metric: mockMetric({
key: MetricKey.conditions_to_cover,
name: 'Conditions to cover',
type: MetricType.ShortInteger,
}),
}),
}),
]}
{...props}
/>,
);
}

+ 45
- 94
server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx View File

@@ -23,34 +23,25 @@ import {
CenteredLayout,
CoverageIndicator,
DuplicationsIndicator,
HelperHintIcon,
Link,
Spinner,
TextMuted,
} from 'design-system';
import { uniq } from 'lodash';
import * as React from 'react';
import { useEffect, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { getMeasuresWithMetrics } from '../../../api/measures';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import { duplicationRatingConverter } from '../../../components/measure/utils';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate } from '../../../helpers/l10n';
import { enhanceConditionWithMeasure, enhanceMeasuresWithMetrics } from '../../../helpers/measures';
import { isDefined } from '../../../helpers/types';
import { getQualityGateUrl, getQualityGatesUrl } from '../../../helpers/urls';
import { useBranchStatusQuery } from '../../../queries/branch';
import { PullRequest } from '../../../types/branch-like';
import { IssueType } from '../../../types/issues';
import { Component, MeasureEnhanced } from '../../../types/types';
import MeasuresPanelIssueMeasure from '../branches/MeasuresPanelIssueMeasure';
import MeasuresPanelPercentMeasure from '../branches/MeasuresPanelPercentMeasure';
import BranchQualityGate from '../components/BranchQualityGate';
import IgnoredConditionWarning from '../components/IgnoredConditionWarning';
import MetaTopBar from '../components/MetaTopBar';
import QualityGateConditions from '../components/QualityGateConditions';
import QualityGateStatusHeader from '../components/QualityGateStatusHeader';
import QualityGateStatusPassedView from '../components/QualityGateStatusPassedView';
import SonarLintPromotion from '../components/SonarLintPromotion';
import '../styles.css';
import { MeasurementType, PR_METRICS, Status } from '../utils';
@@ -107,11 +98,6 @@ export default function PullRequestOverview(props: Props) {
return null;
}

const path =
component.qualityGate === undefined
? getQualityGatesUrl()
: getQualityGateUrl(component.qualityGate.name);

const failedConditions = conditions
.filter((condition) => condition.level === 'ERROR')
.map((c) => enhanceConditionWithMeasure(c, measures))
@@ -119,93 +105,58 @@ export default function PullRequestOverview(props: Props) {

return (
<CenteredLayout>
<div className="it__pr-overview sw-mt-12">
<MetaTopBar branchLike={branchLike} measures={measures} />
<BasicSeparator className="sw-my-4" />

{ignoredConditions && <IgnoredConditionWarning />}

<div className="sw-flex sw-flex-col sw-mr-12 width-30">
<Card>
{status && (
<QualityGateStatusHeader
status={status}
failedConditionCount={failedConditions.length}
/>
)}

<div className="sw-flex sw-items-center sw-mb-4">
<TextMuted text={translate('overview.on_new_code_long')} />
<HelpTooltip
className="sw-ml-2"
overlay={
<FormattedMessage
defaultMessage={translate('overview.quality_gate.conditions_on_new_code')}
id="overview.quality_gate.conditions_on_new_code"
values={{
link: <Link to={path}>{translate('overview.quality_gate')}</Link>,
}}
/>
}
>
<HelperHintIcon aria-label="help-tooltip" />
</HelpTooltip>
</div>

{status === Status.OK && failedConditions.length === 0 && (
<QualityGateStatusPassedView />
)}

{status !== Status.OK && <BasicSeparator />}

{failedConditions.length > 0 && (
<div>
<QualityGateConditions
branchLike={branchLike}
collapsible
component={component}
failedConditions={failedConditions}
/>
</div>
)}
</Card>
<SonarLintPromotion qgConditions={conditions} />
</div>

<div className="sw-flex-1">
<div className="sw-grid sw-grid-cols-2 sw-gap-4 sw-mt-4">
{[
IssueType.Bug,
IssueType.CodeSmell,
IssueType.Vulnerability,
IssueType.SecurityHotspot,
].map((type: IssueType) => (
<Card key={type} className="sw-p-8">
<MeasuresPanelIssueMeasure
branchLike={branchLike}
component={component}
isNewCodeTab
measures={measures}
type={type}
/>
</Card>
))}

{[MeasurementType.Coverage, MeasurementType.Duplication].map(
(type: MeasurementType) => (
<div className="it__pr-overview sw-mt-12 sw-grid sw-grid-cols-12">
<div className="sw-col-start-2 sw-col-span-10">
<MetaTopBar branchLike={branchLike} measures={measures} />
<BasicSeparator className="sw-my-4" />

{ignoredConditions && <IgnoredConditionWarning />}

{status && (
<BranchQualityGate
branchLike={branchLike}
component={component}
status={status}
failedConditions={failedConditions}
/>
)}

<div className="sw-flex-1">
<div className="sw-grid sw-grid-cols-2 sw-gap-4 sw-mt-4">
{[
IssueType.Bug,
IssueType.CodeSmell,
IssueType.Vulnerability,
IssueType.SecurityHotspot,
].map((type: IssueType) => (
<Card key={type} className="sw-p-8">
<MeasuresPanelPercentMeasure
<MeasuresPanelIssueMeasure
branchLike={branchLike}
component={component}
isNewCodeTab
measures={measures}
ratingIcon={renderMeasureIcon(type)}
type={type}
useDiffMetric
/>
</Card>
),
)}
))}

{[MeasurementType.Coverage, MeasurementType.Duplication].map(
(type: MeasurementType) => (
<Card key={type} className="sw-p-8">
<MeasuresPanelPercentMeasure
branchLike={branchLike}
component={component}
measures={measures}
ratingIcon={renderMeasureIcon(type)}
type={type}
useDiffMetric
/>
</Card>
),
)}
</div>
</div>
<SonarLintPromotion qgConditions={conditions} />
</div>
</div>
</CenteredLayout>

+ 14
- 8
server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/PullRequestOverview-it.tsx View File

@@ -26,6 +26,7 @@ import { mockComponent } from '../../../../helpers/mocks/component';
import { mockQualityGateProjectCondition } from '../../../../helpers/mocks/quality-gates';
import { mockLoggedInUser, mockMeasure, mockMetric } from '../../../../helpers/testMocks';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { byLabelText, byRole } from '../../../../helpers/testSelector';
import { ComponentPropsType } from '../../../../helpers/testUtils';
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey, MetricType } from '../../../../types/metrics';
@@ -121,6 +122,8 @@ it('should render correctly for a passed QG', async () => {
renderPullRequestOverview();

await waitFor(async () => expect(await screen.findByText('metric.level.OK')).toBeInTheDocument());
expect(screen.getByLabelText('overview.quality_gate_x.overview.gate.OK')).toBeInTheDocument();

expect(screen.getByText('metric.new_lines.name')).toBeInTheDocument();
expect(screen.getByText(/overview.last_analysis_x/)).toBeInTheDocument();
});
@@ -165,18 +168,21 @@ it('should render correctly for a failed QG', async () => {
renderPullRequestOverview();

await waitFor(async () =>
expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument(),
expect(
await byLabelText('overview.quality_gate_x.overview.gate.ERROR').find(),
).toBeInTheDocument(),
);

expect(await screen.findByText('1.0% metric.new_coverage.name')).toBeInTheDocument();
expect(await screen.findByText('quality_gates.operator.GT 2.0%')).toBeInTheDocument();

expect(
await screen.findByText('1.0% metric.duplicated_lines.name quality_gates.conditions.new_code'),
byRole('link', {
name: 'overview.failed_condition.x_required 10.0% duplicated_lines ≤ 1.0%',
}).get(),
).toBeInTheDocument();
expect(
byRole('link', {
name: 'overview.failed_condition.x_required 10 new_bugs ≤ 3',
}).get(),
).toBeInTheDocument();
expect(await screen.findByText('quality_gates.operator.GT 1.0%')).toBeInTheDocument();

expect(screen.getByText('quality_gates.operator.GT 3')).toBeInTheDocument();
});

function renderPullRequestOverview(

+ 30
- 1
server/sonar-web/src/main/js/apps/overview/utils.ts View File

@@ -26,7 +26,7 @@ import { parseAsString } from '../../helpers/query';
import { IssueType } from '../../types/issues';
import { MetricKey } from '../../types/metrics';
import { AnalysisMeasuresVariations, MeasureHistory } from '../../types/project-activity';
import { RawQuery } from '../../types/types';
import { Dict, RawQuery } from '../../types/types';

export const METRICS: string[] = [
// quality gate
@@ -152,6 +152,35 @@ const MEASUREMENTS_MAP = {
},
};

export const RATING_TO_SEVERITIES_MAPPING = [
'BLOCKER,CRITICAL,MAJOR,MINOR',
'BLOCKER,CRITICAL,MAJOR',
'BLOCKER,CRITICAL',
'BLOCKER',
];

export const RATING_METRICS_MAPPING: Dict<IssueType> = {
[MetricKey.reliability_rating]: IssueType.Bug,
[MetricKey.new_reliability_rating]: IssueType.Bug,
[MetricKey.security_rating]: IssueType.Vulnerability,
[MetricKey.new_security_rating]: IssueType.Vulnerability,
[MetricKey.sqale_rating]: IssueType.CodeSmell,
[MetricKey.new_maintainability_rating]: IssueType.CodeSmell,
[MetricKey.security_review_rating]: IssueType.SecurityHotspot,
[MetricKey.new_security_review_rating]: IssueType.SecurityHotspot,
};

export const METRICS_REPORTED_IN_OVERVIEW_CARDS = [
MetricKey.new_violations,
MetricKey.violations,
MetricKey.new_coverage,
MetricKey.coverage,
MetricKey.new_security_hotspots_reviewed,
MetricKey.security_hotspots_reviewed,
MetricKey.new_duplicated_lines_density,
MetricKey.duplicated_lines_density,
];

export function getIssueRatingName(type: IssueType) {
return translate('metric_domain', ISSUETYPE_METRIC_KEYS_MAP[type].ratingName);
}

+ 7
- 3
server/sonar-web/src/main/js/helpers/testMocks.ts View File

@@ -34,6 +34,7 @@ import { RuleRepository } from '../types/coding-rules';
import { EditionKey } from '../types/editions';
import { IssueScope, IssueSeverity, IssueStatus, IssueType, RawIssue } from '../types/issues';
import { Language } from '../types/languages';
import { MetricKey, MetricType } from '../types/metrics';
import { Notification } from '../types/notifications';
import { DumpStatus, DumpTask } from '../types/project-dump';
import { TaskStatuses } from '../types/tasks';
@@ -395,11 +396,14 @@ export function mockLocation(overrides: Partial<Location> = {}): Location {
};
}

export function mockMetric(overrides: Partial<Pick<Metric, 'key' | 'name' | 'type'>> = {}): Metric {
const key = overrides.key || 'coverage';
export function mockMetric(
overrides: Partial<Pick<Metric, 'key' | 'name' | 'type' | 'domain'>> = {},
): Metric {
const key = overrides.key || MetricKey.coverage;
const name = overrides.name || key;
const type = overrides.type || 'PERCENT';
const type = overrides.type || MetricType.Percent;
return {
...overrides,
id: key,
key,
name,

+ 6
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -2897,6 +2897,7 @@ metric.ncloc_language_distribution.description=Non Commenting Lines of Code Dist
metric.ncloc_language_distribution.name=Lines of Code Per Language
metric.new_blocker_violations.description=New Blocker issues
metric.new_blocker_violations.name=New Blocker Issues
metric.new_blocker_violations.short_name=Blocker Issues
metric.new_branch_coverage.description=Condition coverage of new/changed code
metric.new_branch_coverage.name=Condition Coverage on New Code
metric.new_branch_coverage.extra_short_name=Condition Coverage
@@ -2915,6 +2916,7 @@ metric.new_coverage.name=Coverage on New Code
metric.new_coverage.short_name=Coverage
metric.new_critical_violations.description=New Critical issues
metric.new_critical_violations.name=New Critical Issues
metric.new_critical_violations.short_name=Critical Issues
metric.new_development_cost.description=Development cost on new code
metric.new_development_cost.name=Development Cost on New Code
metric.new_duplicated_blocks.name=Duplicated Blocks on New Code
@@ -2930,6 +2932,7 @@ metric.new_duplicated_lines_density.short_name=Duplications
metric.new_duplicated_lines_density.extra_short_name=Density
metric.new_info_violations.description=New Info issues
metric.new_info_violations.name=New Info Issues
metric.new_info_violations.short_name=Info Issues
metric.new_it_branch_coverage.description=Integration tests condition coverage of new/changed code
metric.new_it_branch_coverage.name=Condition Coverage by IT on New Code
metric.new_it_conditions_to_cover.description=New conditions to cover by integration tests
@@ -2955,8 +2958,10 @@ metric.new_maintainability_rating.name=Maintainability Rating on New Code
metric.new_maintainability_rating.extra_short_name=Rating
metric.new_major_violations.description=New Major issues
metric.new_major_violations.name=New Major Issues
metric.new_major_violations.short_name=Major Issues
metric.new_minor_violations.description=New Minor issues
metric.new_minor_violations.name=New Minor Issues
metric.new_minor_violations.short_name=Minor Issues
metric.new_lines.name=New Lines
metric.new_lines.description=New lines
metric.new_lines.short_name=Lines
@@ -3740,6 +3745,7 @@ system.version_is_availble={version} is available
#------------------------------------------------------------------------------
overview.1_condition_failed=1 failed condition
overview.X_conditions_failed={0} failed conditions
overview.failed_condition.x_required={metric} required {threshold}
overview.fix_failed_conditions_with_sonarlint=Fix issues before they fail your Quality Gate with {link} in your IDE. Power up with connected mode!
overview.quality_gate.status=Quality Gate Status
overview.quality_gate=Quality Gate

Loading…
Cancel
Save