diff options
author | Mathieu Suen <mathieu.suen@sonarsource.com> | 2021-09-30 16:57:18 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-10-01 20:03:19 +0000 |
commit | fe58d75d14ca6bc0005c6110a32d648ffdd361d1 (patch) | |
tree | 6d213f7019bb9310b9998b653941570e07de2206 /server/sonar-web/src | |
parent | abb09faf00e8ea3b03642705ca2d42440336bf28 (diff) | |
download | sonarqube-fe58d75d14ca6bc0005c6110a32d648ffdd361d1.tar.gz sonarqube-fe58d75d14ca6bc0005c6110a32d648ffdd361d1.zip |
SONAR-15458 Add SonarLint promotion for failed Quality Gate
Diffstat (limited to 'server/sonar-web/src')
12 files changed, 425 insertions, 5 deletions
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx index 161e1c686eb..2954827ba40 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx +++ b/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> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx index 5a557597687..3470d679423 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx +++ b/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; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap index 580ccebe664..4c5d74724c9 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap +++ b/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> `; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanelSection-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanelSection-test.tsx.snap index 99121173ca4..84bd6cb3285 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanelSection-test.tsx.snap +++ b/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 [ diff --git a/server/sonar-web/src/main/js/apps/overview/components/SonarLintPromotion.tsx b/server/sonar-web/src/main/js/apps/overview/components/SonarLintPromotion.tsx new file mode 100644 index 00000000000..32a6ce2a627 --- /dev/null +++ b/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); diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarLintPromition-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/SonarLintPromition-test.tsx new file mode 100644 index 00000000000..261794c900a --- /dev/null +++ b/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} />); +} diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/SonarLintPromition-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/SonarLintPromition-test.tsx.snap new file mode 100644 index 00000000000..e0d43e8cda1 --- /dev/null +++ b/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> +`; diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx index 8c542df49e1..d6a0cdcb7ef 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx +++ b/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 && ( diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap index 8af79d11f7e..c3348c42010 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap +++ b/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" diff --git a/server/sonar-web/src/main/js/apps/overview/styles.css b/server/sonar-web/src/main/js/apps/overview/styles.css index a91b526a3c1..48cc58fa9f1 100644 --- a/server/sonar-web/src/main/js/apps/overview/styles.css +++ b/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; } diff --git a/server/sonar-web/src/main/js/components/icons/SonarLintIcon.tsx b/server/sonar-web/src/main/js/components/icons/SonarLintIcon.tsx new file mode 100644 index 00000000000..c4a1dcb7579 --- /dev/null +++ b/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> + ); +} diff --git a/server/sonar-web/src/main/js/types/types.d.ts b/server/sonar-web/src/main/js/types/types.d.ts index 9391ec3b94b..8173bca1ff1 100644 --- a/server/sonar-web/src/main/js/types/types.d.ts +++ b/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 { |