浏览代码

SONAR-15458 Add SonarLint promotion for failed Quality Gate

tags/9.2.0.49834
Mathieu Suen 2 年前
父节点
当前提交
fe58d75d14

+ 5
- 0
server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx 查看文件

@@ -18,12 +18,14 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
import { flatMap } from 'lodash';
import * as React from 'react';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import { Alert } from '../../../components/ui/Alert';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { QualityGateStatus } from '../../../types/quality-gates';
import SonarLintPromotion from '../components/SonarLintPromotion';
import QualityGatePanelSection from './QualityGatePanelSection';

export interface QualityGatePanelProps {
@@ -116,6 +118,9 @@ export function QualityGatePanel(props: QualityGatePanelProps) {
</>
)}
</div>
<SonarLintPromotion
qgConditions={flatMap(qgStatuses, qgStatus => qgStatus.failedConditions)}
/>
</div>
);
}

+ 1
- 1
server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx 查看文件

@@ -23,7 +23,7 @@ import { isDiffMetric } from '../../../helpers/measures';
import { BranchLike } from '../../../types/branch-like';
import { ComponentQualifier } from '../../../types/component';
import { QualityGateStatus } from '../../../types/quality-gates';
import { QualityGateConditions } from '../components/QualityGateConditions';
import QualityGateConditions from '../components/QualityGateConditions';

export interface QualityGatePanelSectionProps {
branchLike?: BranchLike;

+ 165
- 0
server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap 查看文件

@@ -182,6 +182,81 @@ exports[`should render correctly for applications 1`] = `
/>
</div>
</div>
<Connect(withCurrentUser(SonarLintPromotion))
qgConditions={
Array [
Object {
"actual": "10",
"error": "0",
"level": "ERROR",
"measure": Object {
"bestValue": true,
"leak": "1",
"metric": Object {
"id": "coverage",
"key": "coverage",
"name": "Coverage",
"type": "PERCENT",
},
"period": Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
"value": "1.0",
},
"metric": "foo",
"op": "GT",
},
Object {
"actual": "10",
"error": "0",
"level": "ERROR",
"measure": Object {
"bestValue": true,
"leak": "1",
"metric": Object {
"id": "coverage",
"key": "coverage",
"name": "Coverage",
"type": "PERCENT",
},
"period": Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
"value": "1.0",
},
"metric": "foo",
"op": "GT",
},
Object {
"actual": "10",
"error": "0",
"level": "ERROR",
"measure": Object {
"bestValue": true,
"leak": "1",
"metric": Object {
"id": "new_code_smells",
"key": "new_code_smells",
"name": "New_code_smells",
"type": "PERCENT",
},
"period": Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
"value": "1.0",
},
"metric": "new_code_smells",
"op": "GT",
},
]
}
/>
</div>
`;

@@ -320,6 +395,35 @@ exports[`should render correctly for applications 2`] = `
/>
</div>
</div>
<Connect(withCurrentUser(SonarLintPromotion))
qgConditions={
Array [
Object {
"actual": "10",
"error": "0",
"level": "ERROR",
"measure": Object {
"bestValue": true,
"leak": "1",
"metric": Object {
"id": "coverage",
"key": "coverage",
"name": "Coverage",
"type": "PERCENT",
},
"period": Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
"value": "1.0",
},
"metric": "foo",
"op": "GT",
},
]
}
/>
</div>
`;

@@ -424,6 +528,35 @@ exports[`should render correctly for projects 1`] = `
/>
</div>
</div>
<Connect(withCurrentUser(SonarLintPromotion))
qgConditions={
Array [
Object {
"actual": "10",
"error": "0",
"level": "ERROR",
"measure": Object {
"bestValue": true,
"leak": "1",
"metric": Object {
"id": "coverage",
"key": "coverage",
"name": "Coverage",
"type": "PERCENT",
},
"period": Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
"value": "1.0",
},
"metric": "foo",
"op": "GT",
},
]
}
/>
</div>
`;

@@ -466,6 +599,9 @@ exports[`should render correctly for projects 2`] = `
</span>
</div>
</div>
<Connect(withCurrentUser(SonarLintPromotion))
qgConditions={Array []}
/>
</div>
`;

@@ -585,5 +721,34 @@ exports[`should render correctly for projects 3`] = `
/>
</div>
</div>
<Connect(withCurrentUser(SonarLintPromotion))
qgConditions={
Array [
Object {
"actual": "10",
"error": "0",
"level": "ERROR",
"measure": Object {
"bestValue": true,
"leak": "1",
"metric": Object {
"id": "coverage",
"key": "coverage",
"name": "Coverage",
"type": "PERCENT",
},
"period": Object {
"bestValue": true,
"index": 1,
"value": "1.0",
},
"value": "1.0",
},
"metric": "foo",
"op": "GT",
},
]
}
/>
</div>
`;

+ 4
- 4
server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanelSection-test.tsx.snap 查看文件

@@ -9,7 +9,7 @@ exports[`should render correctly 1`] = `
>
quality_gates.conditions.new_code
</h4>
<QualityGateConditions
<Memo(QualityGateConditions)
component={
Object {
"failedConditions": Array [
@@ -99,7 +99,7 @@ exports[`should render correctly 1`] = `
>
quality_gates.conditions.overall_code
</h4>
<QualityGateConditions
<Memo(QualityGateConditions)
component={
Object {
"failedConditions": Array [
@@ -201,7 +201,7 @@ exports[`should render correctly 2`] = `
>
quality_gates.conditions.new_code
</h4>
<QualityGateConditions
<Memo(QualityGateConditions)
component={
Object {
"failedConditions": Array [
@@ -291,7 +291,7 @@ exports[`should render correctly 2`] = `
>
quality_gates.conditions.overall_code
</h4>
<QualityGateConditions
<Memo(QualityGateConditions)
component={
Object {
"failedConditions": Array [

+ 79
- 0
server/sonar-web/src/main/js/apps/overview/components/SonarLintPromotion.tsx 查看文件

@@ -0,0 +1,79 @@
/*
* SonarQube
* Copyright (C) 2009-2021 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 { FormattedMessage } from 'react-intl';
import { withCurrentUser } from '../../../components/hoc/withCurrentUser';
import SonarLintIcon from '../../../components/icons/SonarLintIcon';
import { translate } from '../../../helpers/l10n';
import { MetricKey } from '../../../types/metrics';
import { QualityGateStatusCondition } from '../../../types/quality-gates';

export interface SonarLintPromotionProps {
currentUser: T.CurrentUser;
qgConditions?: QualityGateStatusCondition[];
}

const CONDITIONS_TO_SHOW = [
MetricKey.new_blocker_violations,
MetricKey.new_critical_violations,
MetricKey.new_info_violations,
MetricKey.new_violations,
MetricKey.new_major_violations,
MetricKey.new_minor_violations,
MetricKey.new_code_smells,
MetricKey.new_bugs,
MetricKey.new_vulnerabilities,
MetricKey.new_security_rating,
MetricKey.new_maintainability_rating,
MetricKey.new_reliability_rating
];

export function SonarLintPromotion({ currentUser, qgConditions }: SonarLintPromotionProps) {
const showMessage = qgConditions?.some(
qgCondition =>
CONDITIONS_TO_SHOW.includes(qgCondition.metric as MetricKey) && qgCondition.level === 'ERROR'
);
if (!showMessage || currentUser.usingSonarLintConnectedMode) {
return null;
}
return (
<div className="it__overview__sonarlint-promotion big-spacer-top overview-quality-gate-sonar-lint-info">
<FormattedMessage
id="overview.fix_failed_conditions_with_sonarlint"
defaultMessage={translate('overview.fix_failed_conditions_with_sonarlint')}
values={{
link: (
<>
<a
href="https://www.sonarqube.org/sonarlint/?referrer=sonarqube"
rel="noopener noreferrer"
target="_blank">
SonarLint
</a>
<SonarLintIcon size={16} />
</>
)
}}
/>
</div>
);
}

export default withCurrentUser(SonarLintPromotion);

+ 63
- 0
server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarLintPromition-test.tsx 查看文件

@@ -0,0 +1,63 @@
/*
* SonarQube
* Copyright (C) 2009-2021 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 { shallow } from 'enzyme';
import * as React from 'react';
import { mockQualityGateStatusCondition } from '../../../../helpers/mocks/quality-gates';
import { mockCurrentUser } from '../../../../helpers/testMocks';
import { MetricKey } from '../../../../types/metrics';
import { SonarLintPromotion, SonarLintPromotionProps } from '../SonarLintPromotion';

it('should render correctly', () => {
expect(shallowRender().type()).toBeNull();
expect(
shallowRender({ currentUser: mockCurrentUser({ usingSonarLintConnectedMode: true }) }).type()
).toBeNull();
expect(
shallowRender({
qgConditions: [mockQualityGateStatusCondition({ metric: MetricKey.new_bugs, level: 'ERROR' })]
})
).toMatchSnapshot('has failed condition');
});

it.each(
[
MetricKey.new_blocker_violations,
MetricKey.new_critical_violations,
MetricKey.new_info_violations,
MetricKey.new_violations,
MetricKey.new_major_violations,
MetricKey.new_minor_violations,
MetricKey.new_code_smells,
MetricKey.new_bugs,
MetricKey.new_vulnerabilities,
MetricKey.new_security_rating,
MetricKey.new_maintainability_rating,
MetricKey.new_reliability_rating
].map(Array.of)
)('should show message for %s', metric => {
const wrapper = shallowRender({
qgConditions: [mockQualityGateStatusCondition({ metric: metric as string })]
});
expect(wrapper.type()).not.toBeNull();
});

function shallowRender(props: Partial<SonarLintPromotionProps> = {}) {
return shallow(<SonarLintPromotion currentUser={mockCurrentUser()} {...props} />);
}

+ 28
- 0
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/SonarLintPromition-test.tsx.snap 查看文件

@@ -0,0 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: has failed condition 1`] = `
<div
className="it__overview__sonarlint-promotion big-spacer-top overview-quality-gate-sonar-lint-info"
>
<FormattedMessage
defaultMessage="overview.fix_failed_conditions_with_sonarlint"
id="overview.fix_failed_conditions_with_sonarlint"
values={
Object {
"link": <React.Fragment>
<a
href="https://www.sonarqube.org/sonarlint/?referrer=sonarqube"
rel="noopener noreferrer"
target="_blank"
>
SonarLint
</a>
<SonarLintIcon
size={16}
/>
</React.Fragment>,
}
}
/>
</div>
`;

+ 3
- 0
server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx 查看文件

@@ -37,6 +37,7 @@ import IssueLabel from '../components/IssueLabel';
import IssueRating from '../components/IssueRating';
import MeasurementLabel from '../components/MeasurementLabel';
import QualityGateConditions from '../components/QualityGateConditions';
import SonarLintPromotion from '../components/SonarLintPromotion';
import '../styles.css';
import { MeasurementType, PR_METRICS } from '../utils';
import AfterMergeEstimate from './AfterMergeEstimate';
@@ -197,6 +198,8 @@ export class PullRequestOverview extends React.PureComponent<Props, State> {
/>
</h2>
<LargeQualityGateBadge component={component} level={status} />

<SonarLintPromotion qgConditions={conditions} />
</div>

{failedConditions.length > 0 && (

+ 25
- 0
server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap 查看文件

@@ -53,6 +53,28 @@ exports[`should render correctly for a failed QG 1`] = `
}
level="ERROR"
/>
<Connect(withCurrentUser(SonarLintPromotion))
qgConditions={
Array [
Object {
"actual": "10",
"error": "1.0",
"level": "OK",
"metric": "new_bugs",
"op": "GT",
"period": 1,
},
Object {
"actual": "10",
"error": "1.0",
"level": "ERROR",
"metric": "new_code_smells",
"op": "GT",
"period": 1,
},
]
}
/>
</div>
<div
className="pr-overview-failed-conditions big-spacer-right"
@@ -1402,6 +1424,9 @@ exports[`should render correctly for a passed QG 1`] = `
}
level="OK"
/>
<Connect(withCurrentUser(SonarLintPromotion))
qgConditions={Array []}
/>
</div>
<div
className="flex-1"

+ 9
- 0
server/sonar-web/src/main/js/apps/overview/styles.css 查看文件

@@ -30,6 +30,11 @@
border: 1px solid var(--barBorderColor);
}

.overview-quality-gate-sonar-lint-info {
padding: 8px 16px;
border: 1px solid var(--barBorderColor);
}

.overview-panel-title {
text-transform: uppercase;
font-weight: 600;
@@ -249,6 +254,10 @@
color: var(--transparentWhite);
}

.pr-overview .overview-quality-gate-sonar-lint-info {
width: 207px;
}

.pr-pverview .overview-measures-row {
min-height: 85px;
}

+ 42
- 0
server/sonar-web/src/main/js/components/icons/SonarLintIcon.tsx 查看文件

@@ -0,0 +1,42 @@
/*
* SonarQube
* Copyright (C) 2009-2021 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 Icon, { IconProps } from './Icon';

export default function SonarLintIcon(props: IconProps) {
return (
<Icon {...props} viewBox="0 0 512 512">
<defs>
<path id="a" d="M0 0h512v512H0z" />
</defs>
<clipPath id="b">
<use xlinkHref="#a" style={{ overflow: 'visible' }} />
</clipPath>
<path
d="M255.7 450.7c-107.8 0-195.5-87.6-195.5-195.3 0-107.7 87.7-195.3 195.5-195.3s195.5 87.6 195.5 195.3c0 107.7-87.7 195.3-195.5 195.3m0-355.2c-88.2 0-160 71.7-160 159.8 0 88.1 71.8 159.8 160 159.8s160-71.7 160-159.8c0-88.1-71.8-159.8-160-159.8"
style={{ clipPath: 'url(#b)', fill: '#cb2029' }}
/>
<path
d="M392.6 244.9c-5.1-10.2-11.5-23-24.1-23-12.5 0-18.9 12.7-24.1 23-4.8 9.5-7.5 14.5-13.5 14.5s-8.7-5-13.5-14.5c-5.1-10.2-11.5-23-24.1-23s-18.9 12.7-24.1 23c-4.8 9.5-7.5 14.5-13.5 14.5s-8.7-5-13.5-14.5c-5.1-10.2-11.5-23-24.1-23s-18.9 12.7-24.1 23c-4.8 9.5-7.5 14.5-13.5 14.5s-8.7-5-13.5-14.5c-5.1-10.2-11.5-23-24.1-23-12.5 0-18.9 12.1-24 22.3 0 0-3.9 8.7-5.6 12.8-1.7 4.1-8.3 19.7-2.3 27.2 3.4 4.2 8.3-1.8 11.1-6.2 2.1-3.4 7.1-13.2 7.1-13.2 5.1-9.1 7.8-14 13.7-14 6 0 8.7 5 13.5 14.5 5.1 10.2 11.5 23 24.1 23 12.5 0 18.9-12.7 24.1-23 4.8-9.5 7.5-14.5 13.5-14.5s8.7 5 13.5 14.5c5.1 10.2 11.5 23 24.1 23 12.5 0 18.9-12.7 24.1-23 4.8-9.5 7.5-14.5 13.5-14.5s8.7 5 13.5 14.5c5.1 10.2 11.5 23 24.1 23s18.9-12.7 24.1-23c4.8-9.5 7.5-14.5 13.5-14.5s8.7 5 13.5 14.5c0 0 2.2 4.3 5.4 9.9 1.4 2.3 6.6 11.1 11.2 10.8 4.1-.3 5.6-13.3 1-24.7-3.1-7.6-7-16.4-7-16.4z"
style={{ fill: '#cb2029' }}
/>
</Icon>
);
}

+ 1
- 0
server/sonar-web/src/main/js/types/types.d.ts 查看文件

@@ -218,6 +218,7 @@ declare namespace T {
export interface CurrentUser {
isLoggedIn: boolean;
permissions?: { global: string[] };
usingSonarLintConnectedMode?: boolean;
}

export interface CurrentUserSetting {

+ 1
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties 查看文件

@@ -2997,6 +2997,7 @@ system.version_is_availble={version} is available
overview.failed_conditions=Failed conditions
overview.X_more_failed_conditions={0} more failed conditions
overview.X_conditions_failed={0} conditions failed
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=Quality Gate Status
overview.quality_gate.help=A Quality Gate is a set of measure-based Boolean conditions. It helps you know immediately whether your project is production-ready. If your current status is not Passed, you'll see which measures caused the problem and the values required to pass.
overview.quality_gate_failed_with_x=with {0} errors

正在加载...
取消
保存