Browse Source

SONAR-17815 implement updated logic for CaYC quality gates

tags/10.0.0.68432
Matteo Mara 1 year ago
parent
commit
2d60913db1
44 changed files with 727 additions and 250 deletions
  1. 1
    1
      server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java
  2. 1
    1
      server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java
  3. 6
    5
      server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java
  4. 39
    7
      server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts
  5. 86
    0
      server/sonar-web/src/main/js/apps/overview/branches/ApplicationNonCaycProjectWarning.tsx
  6. 4
    4
      server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx
  7. 19
    11
      server/sonar-web/src/main/js/apps/overview/branches/CleanAsYouCodeWarning.tsx
  8. 62
    0
      server/sonar-web/src/main/js/apps/overview/branches/CleanAsYouCodeWarningOverCompliant.tsx
  9. 21
    40
      server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx
  10. 17
    8
      server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx
  11. 24
    6
      server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-test.tsx
  12. 3
    1
      server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx
  13. 6
    6
      server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap
  14. 4
    4
      server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanelSection-test.tsx.snap
  15. 35
    0
      server/sonar-web/src/main/js/apps/quality-gates/components/CaycOverCompliantBadgeTooltip.tsx
  16. 36
    7
      server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx
  17. 50
    30
      server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx
  18. 22
    5
      server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValue.tsx
  19. 46
    19
      server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
  20. 3
    3
      server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx
  21. 10
    7
      server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx
  22. 2
    2
      server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx
  23. 25
    1
      server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx
  24. 16
    0
      server/sonar-web/src/main/js/apps/quality-gates/styles.css
  25. 20
    18
      server/sonar-web/src/main/js/apps/quality-gates/utils.ts
  26. 5
    5
      server/sonar-web/src/main/js/helpers/mocks/quality-gates.ts
  27. 4
    4
      server/sonar-web/src/main/js/types/quality-gates.ts
  28. 7
    1
      server/sonar-web/src/main/js/types/types.ts
  29. 1
    1
      server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java
  30. 7
    4
      server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java
  31. 15
    6
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateCaycChecker.java
  32. 38
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateCaycStatus.java
  33. 2
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/ListAction.java
  34. 4
    3
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/ProjectStatusAction.java
  35. 6
    5
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java
  36. 6
    5
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/ShowAction.java
  37. 1
    1
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/project_status-example.json
  38. 9
    6
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateCaycCheckerTest.java
  39. 18
    6
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/ListActionTest.java
  40. 11
    3
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/ProjectStatusActionTest.java
  41. 4
    3
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest.java
  42. 10
    2
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/ShowActionTest.java
  43. 18
    4
      sonar-core/src/main/resources/org/sonar/l10n/core.properties
  44. 3
    3
      sonar-ws/src/main/protobuf/ws-qualitygates.proto

+ 1
- 1
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryData.java View File

@@ -280,7 +280,7 @@ public class TelemetryData {
record Project(String projectUuid, Long lastAnalysis, String language, Long loc) {
}

record QualityGate(String uuid, boolean isCaycCompliant) {
record QualityGate(String uuid, String caycStatus) {
}

public static class ProjectStatistics {

+ 1
- 1
server/sonar-server-common/src/main/java/org/sonar/server/telemetry/TelemetryDataJsonWriter.java View File

@@ -174,7 +174,7 @@ public class TelemetryDataJsonWriter {
statistics.getQualityGates().forEach(qualityGate -> {
json.beginObject();
json.prop("uuid", qualityGate.uuid());
json.prop("isCaycCompliant", qualityGate.isCaycCompliant());
json.prop("caycStatus", qualityGate.caycStatus());
json.endObject();
});
json.endArray();

+ 6
- 5
server/sonar-server-common/src/test/java/org/sonar/server/telemetry/TelemetryDataJsonWriterTest.java View File

@@ -453,15 +453,15 @@ public class TelemetryDataJsonWriterTest {
"quality-gates": [
{
"uuid": "uuid-0",
"isCaycCompliant": true
"caycStatus": "non-compliant"
},
{
"uuid": "uuid-1",
"isCaycCompliant": false
"caycStatus": "compliant"
},
{
"uuid": "uuid-2",
"isCaycCompliant": true
"caycStatus": "over-compliant"
}
]
}
@@ -518,8 +518,9 @@ public class TelemetryDataJsonWriterTest {
}

private List<TelemetryData.QualityGate> attachQualityGates() {
return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.QualityGate("uuid-" + i, i % 2 == 0))
.collect(Collectors.toList());
return List.of(new TelemetryData.QualityGate("uuid-0", "non-compliant"),
new TelemetryData.QualityGate("uuid-1", "compliant"),
new TelemetryData.QualityGate("uuid-2", "over-compliant"));
}

@DataProvider

+ 39
- 7
server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts View File

@@ -23,7 +23,7 @@ import { mockQualityGate } from '../../helpers/mocks/quality-gates';
import { mockUserBase } from '../../helpers/mocks/users';
import { mockCondition, mockGroup } from '../../helpers/testMocks';
import { MetricKey } from '../../types/metrics';
import { Condition, QualityGate } from '../../types/types';
import { CaycStatus, Condition, QualityGate } from '../../types/types';
import {
addGroup,
addUser,
@@ -84,7 +84,7 @@ export class QualityGatesServiceMock {
],
isDefault: true,
isBuiltIn: false,
isCaycCompliant: true,
caycStatus: CaycStatus.Compliant,
}),
mockQualityGate({
name: 'SonarSource way - CFamily',
@@ -95,6 +95,7 @@ export class QualityGatesServiceMock {
],
isDefault: false,
isBuiltIn: false,
caycStatus: CaycStatus.NonCompliant,
}),
mockQualityGate({
name: 'Sonar way',
@@ -123,7 +124,7 @@ export class QualityGatesServiceMock {
],
isDefault: false,
isBuiltIn: true,
isCaycCompliant: true,
caycStatus: CaycStatus.Compliant,
}),
mockQualityGate({
name: 'Non Cayc QG',
@@ -134,14 +135,45 @@ export class QualityGatesServiceMock {
],
isDefault: false,
isBuiltIn: false,
isCaycCompliant: false,
caycStatus: CaycStatus.NonCompliant,
}),
mockQualityGate({
name: 'Over Compliant CAYC QG',
conditions: [
{ id: 'deprecatedoc', metric: 'function_complexity', op: 'LT', error: '1' },
{ id: 'AXJMbIUHPAOIsUIE3eOFoc', metric: 'new_coverage', op: 'LT', error: '80' },
{ id: 'AXJMbIUHPAOIsUIE3eNsoc', metric: 'new_security_rating', op: 'GT', error: '1' },
{ id: 'AXJMbIUHPAOIsUIE3eODoc', metric: 'new_reliability_rating', op: 'GT', error: '1' },
{
id: 'AXJMbIUHPAOIsUIE3eOEoc',
metric: 'new_maintainability_rating',
op: 'GT',
error: '1',
},
{ id: 'AXJMbIUHPAOIsUIE3eOFocdl', metric: 'new_coverage', op: 'LT', error: '80' },
{
id: 'AXJMbIUHPAOIsUIE3eOGoc',
metric: 'new_duplicated_lines_density',
op: 'GT',
error: '3',
},
{
id: 'AXJMbIUHPAOIsUIE3eOkoc',
metric: 'new_security_hotspots_reviewed',
op: 'LT',
error: '100',
},
],
isDefault: false,
isBuiltIn: false,
caycStatus: CaycStatus.OverCompliant,
}),
mockQualityGate({
name: 'QG without conditions',
conditions: [],
isDefault: false,
isBuiltIn: false,
isCaycCompliant: false,
caycStatus: CaycStatus.NonCompliant,
}),
mockQualityGate({
name: 'QG without new code conditions',
@@ -150,7 +182,7 @@ export class QualityGatesServiceMock {
],
isDefault: false,
isBuiltIn: false,
isCaycCompliant: false,
caycStatus: CaycStatus.NonCompliant,
}),
];

@@ -279,7 +311,7 @@ export class QualityGatesServiceMock {
],
isDefault: false,
isBuiltIn: false,
isCaycCompliant: true,
caycStatus: CaycStatus.Compliant,
})
);
return this.reply({

+ 86
- 0
server/sonar-web/src/main/js/apps/overview/branches/ApplicationNonCaycProjectWarning.tsx View File

@@ -0,0 +1,86 @@
/*
* 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 DocLink from '../../../components/common/DocLink';
import Link from '../../../components/common/Link';
import QualifierIcon from '../../../components/icons/QualifierIcon';
import { Alert } from '../../../components/ui/Alert';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getProjectQueryUrl } from '../../../helpers/urls';
import { ComponentQualifier } from '../../../types/component';
import { QualityGateStatus } from '../../../types/quality-gates';
import { CaycStatus } from '../../../types/types';

interface Props {
projects: QualityGateStatus[];
caycStatus: CaycStatus;
}

export default function ApplicationNonCaycProjectWarning({ projects, caycStatus }: Props) {
return (
<div className="overview-quality-gate-conditions-list padded big-spacer-top">
{caycStatus === CaycStatus.NonCompliant ? (
<Alert variant="warning">
{translateWithParameters(
'overview.quality_gate.application.non_cayc.projects_x',
projects.length
)}
</Alert>
) : (
<p className="padded">
{translateWithParameters(
'overview.quality_gate.application.cayc_over_compliant.projects_x',
projects.length
)}
</p>
)}

<ul className="spacer-left spacer-bottom big-spacer-top">
{projects.map(({ key, name, branchLike }) => (
<li key={key} className="text-ellipsis spacer-bottom" title={name}>
<Link
className="link-no-underline"
to={getProjectQueryUrl(key, getBranchLikeQuery(branchLike))}
>
<QualifierIcon
className="little-spacer-right"
qualifier={ComponentQualifier.Project}
/>
{name}
</Link>
</li>
))}
</ul>
<hr className="big-spacer-top big-spacer-bottom" />
<div className="spacer spacer-bottom big-spacer-top">
{caycStatus === CaycStatus.NonCompliant ? (
<DocLink to="/user-guide/clean-as-you-code/">
{translate('overview.quality_gate.conditions.cayc.link')}
</DocLink>
) : (
<DocLink to="/user-guide/clean-as-you-code/#potential-drawbacks">
{translate('overview.quality_gate.conditions.cayc_over_compliant.link')}
</DocLink>
)}
</div>
</div>
);
}

+ 4
- 4
server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx View File

@@ -187,13 +187,13 @@ export default class BranchOverview extends React.PureComponent<Props, State> {
if (this.mounted) {
const qgStatuses = results
.map(({ measures = [], project, projectBranchLike }) => {
const { key, name, status, isCaycCompliant } = project;
const { key, name, status, caycStatus } = project;
const conditions = extractStatusConditionsFromApplicationStatusChildProject(project);
const failedConditions = this.getFailedConditions(conditions, measures);

return {
failedConditions,
isCaycCompliant,
caycStatus,
key,
name,
status,
@@ -241,13 +241,13 @@ export default class BranchOverview extends React.PureComponent<Props, State> {
this.loadMeasuresAndMeta(key, branch, metricKeys).then(
({ measures, metrics, period }) => {
if (this.mounted && measures) {
const { ignoredConditions, isCaycCompliant, status } = projectStatus;
const { ignoredConditions, caycStatus, status } = projectStatus;
const conditions = extractStatusConditionsFromProjectStatus(projectStatus);
const failedConditions = this.getFailedConditions(conditions, measures);

const qgStatus = {
ignoredConditions,
isCaycCompliant,
caycStatus,
failedConditions,
key,
name,

+ 19
- 11
server/sonar-web/src/main/js/apps/overview/branches/CleanAsYouCodeWarning.tsx View File

@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import DocLink from '../../../components/common/DocLink';
import Link from '../../../components/common/Link';
import { Alert } from '../../../components/ui/Alert';
@@ -33,17 +34,24 @@ export default function CleanAsYouCodeWarning({ component }: Props) {
return (
<>
<Alert variant="warning">{translate('overview.quality_gate.conditions.cayc.warning')}</Alert>

<p className="big-spacer-top big-spacer-bottom">
{translate('overview.quality_gate.conditions.cayc.details')}
</p>

{component.qualityGate && (
<div className="big-spacer-bottom">
<Link className="button" to={getQualityGateUrl(component.qualityGate.name)}>
{translate('overview.quality_gate.conditions.cayc.review')}
</Link>
</div>
{component.qualityGate ? (
<p className="big-spacer-top big-spacer-bottom">
<FormattedMessage
id="overview.quality_gate.conditions.cayc.details_with_link"
defaultMessage={translate('overview.quality_gate.conditions.cayc.details_with_link')}
values={{
link: (
<Link to={getQualityGateUrl(component.qualityGate.name)}>
{translate('overview.quality_gate.conditions.non_cayc.warning.link')}
</Link>
),
}}
/>
</p>
) : (
<p className="big-spacer-top big-spacer-bottom">
{translate('overview.quality_gate.conditions.cayc.details')}
</p>
)}

<DocLink to="/user-guide/clean-as-you-code/">

+ 62
- 0
server/sonar-web/src/main/js/apps/overview/branches/CleanAsYouCodeWarningOverCompliant.tsx View File

@@ -0,0 +1,62 @@
/*
* 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 { FormattedMessage } from 'react-intl';
import DocLink from '../../../components/common/DocLink';
import Link from '../../../components/common/Link';
import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls';
import { Component } from '../../../types/types';

interface Props {
component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>;
}

export default function CleanAsYouCodeWarningOverCompliant({ component }: Props) {
return (
<>
{component.qualityGate ? (
<p className="big-spacer-bottom">
<FormattedMessage
id="overview.quality_gate.conditions.cayc_over_compliant.details_with_link"
defaultMessage={translate(
'overview.quality_gate.conditions.cayc_over_compliant.details_with_link'
)}
values={{
link: (
<Link to={getQualityGateUrl(component.qualityGate.name)}>
{translate('overview.quality_gate.conditions.cayc_over_compliant.warning.link')}
</Link>
),
}}
/>
</p>
) : (
<p className="big-spacer-bottom">
{translate('overview.quality_gate.conditions.cayc_over_compliant.details')}
</p>
)}

<DocLink to="/user-guide/clean-as-you-code/#potential-drawbacks">
{translate('overview.quality_gate.conditions.cayc_over_compliant.link')}
</DocLink>
</>
);
}

+ 21
- 40
server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx View File

@@ -20,18 +20,15 @@
import classNames from 'classnames';
import { flatMap } from 'lodash';
import * as React from 'react';
import Link from '../../../components/common/Link';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import QualifierIcon from '../../../components/icons/QualifierIcon';
import { Alert } from '../../../components/ui/Alert';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getProjectQueryUrl } from '../../../helpers/urls';
import { ComponentQualifier, isApplication } from '../../../types/component';
import { QualityGateStatus } from '../../../types/quality-gates';
import { Component } from '../../../types/types';
import { CaycStatus, Component } from '../../../types/types';
import SonarLintPromotion from '../components/SonarLintPromotion';
import ApplicationNonCaycProjectWarning from './ApplicationNonCaycProjectWarning';
import QualityGatePanelSection from './QualityGatePanelSection';

export interface QualityGatePanelProps {
@@ -57,7 +54,13 @@ export function QualityGatePanel(props: QualityGatePanelProps) {

const nonCaycProjectsInApp = isApplication(component.qualifier)
? qgStatuses
.filter(({ isCaycCompliant }) => !isCaycCompliant)
.filter(({ caycStatus }) => caycStatus === CaycStatus.NonCompliant)
.sort(({ name: a }, { name: b }) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
: [];

const overCompliantCaycProjectsInApp = isApplication(component.qualifier)
? qgStatuses
.filter(({ caycStatus }) => caycStatus === CaycStatus.OverCompliant)
.sort(({ name: a }, { name: b }) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
: [];

@@ -118,7 +121,7 @@ export function QualityGatePanel(props: QualityGatePanelProps) {
</div>

{(overallFailedConditionsCount > 0 ||
qgStatuses.some(({ isCaycCompliant }) => !isCaycCompliant)) && (
qgStatuses.some(({ caycStatus }) => caycStatus !== CaycStatus.Compliant)) && (
<div data-test="overview__quality-gate-conditions">
{qgStatuses.map((qgStatus) => (
<QualityGatePanelSection
@@ -131,39 +134,17 @@ export function QualityGatePanel(props: QualityGatePanelProps) {
)}

{nonCaycProjectsInApp.length > 0 && (
<div className="overview-quality-gate-conditions-list padded big-spacer-top">
<Alert variant="warning">
{translateWithParameters(
'overview.quality_gate.application.non_cayc.projects_x',
nonCaycProjectsInApp.length
)}
</Alert>
<div className="spacer big-spacer-bottom big-spacer-top">
<Link
target="_blank"
to="https://docs.sonarqube.org/latest/user-guide/clean-as-you-code/#quality-gate"
>
{translate('overview.quality_gate.conditions.cayc.link')}
</Link>
</div>
<hr className="big-spacer-top big-spacer-bottom" />
<ul className="spacer-left spacer-bottom">
{nonCaycProjectsInApp.map(({ key, name, branchLike }) => (
<li key={key} className="text-ellipsis spacer-bottom" title={name}>
<Link
className="link-no-underline"
to={getProjectQueryUrl(key, getBranchLikeQuery(branchLike))}
>
<QualifierIcon
className="little-spacer-right"
qualifier={ComponentQualifier.Project}
/>
{name}
</Link>
</li>
))}
</ul>
</div>
<ApplicationNonCaycProjectWarning
projects={nonCaycProjectsInApp}
caycStatus={CaycStatus.NonCompliant}
/>
)}

{overCompliantCaycProjectsInApp.length > 0 && (
<ApplicationNonCaycProjectWarning
projects={overCompliantCaycProjectsInApp}
caycStatus={CaycStatus.OverCompliant}
/>
)}
</>
)}

+ 17
- 8
server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanelSection.tsx View File

@@ -29,9 +29,10 @@ import {
QualityGateStatus,
QualityGateStatusConditionEnhanced,
} from '../../../types/quality-gates';
import { Component } from '../../../types/types';
import { CaycStatus, Component } from '../../../types/types';
import QualityGateConditions from '../components/QualityGateConditions';
import CleanAsYouCodeWarning from './CleanAsYouCodeWarning';
import CleanAsYouCodeWarningOverCompliant from './CleanAsYouCodeWarningOverCompliant';

export interface QualityGatePanelSectionProps {
branchLike?: BranchLike;
@@ -85,7 +86,7 @@ export function QualityGatePanelSection(props: QualityGatePanelSectionProps) {
if (
!(
qgStatus.failedConditions.length > 0 ||
(!qgStatus.isCaycCompliant && !isApplication(component.qualifier))
(qgStatus.caycStatus !== CaycStatus.Compliant && !isApplication(component.qualifier))
)
) {
return null;
@@ -99,7 +100,7 @@ export function QualityGatePanelSection(props: QualityGatePanelSectionProps) {

const showSectionTitles =
isApplication(component.qualifier) ||
!qgStatus.isCaycCompliant ||
qgStatus.caycStatus !== CaycStatus.Compliant ||
(overallFailedConditions.length > 0 && newCodeFailedConditions.length > 0);

const toggleLabel = collapsed
@@ -130,11 +131,19 @@ export function QualityGatePanelSection(props: QualityGatePanelSectionProps) {

{!collapsed && (
<>
{!qgStatus.isCaycCompliant && !isApplication(component.qualifier) && (
<div className="big-padded bordered-bottom overview-quality-gate-conditions-list">
<CleanAsYouCodeWarning component={component} />
</div>
)}
{qgStatus.caycStatus === CaycStatus.NonCompliant &&
!isApplication(component.qualifier) && (
<div className="big-padded bordered-bottom overview-quality-gate-conditions-list">
<CleanAsYouCodeWarning component={component} />
</div>
)}

{qgStatus.caycStatus === CaycStatus.OverCompliant &&
!isApplication(component.qualifier) && (
<div className="big-padded bordered-bottom overview-quality-gate-conditions-list">
<CleanAsYouCodeWarningOverCompliant component={component} />
</div>
)}

{newCodeFailedConditions.length > 0 && (
<>

+ 24
- 6
server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-test.tsx View File

@@ -42,7 +42,7 @@ import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey } from '../../../../types/metrics';
import { GraphType } from '../../../../types/project-activity';
import { Measure, Metric } from '../../../../types/types';
import { CaycStatus, Measure, Metric } from '../../../../types/types';
import BranchOverview, { BRANCH_OVERVIEW_ACTIVITY_GRAPH, NO_CI_DETECTED } from '../BranchOverview';

jest.mock('../../../../api/measures', () => {
@@ -223,7 +223,7 @@ describe('project overview', () => {
jest
.mocked(getQualityGateProjectStatus)
.mockResolvedValueOnce(
mockQualityGateProjectStatus({ status: 'OK', isCaycCompliant: false })
mockQualityGateProjectStatus({ status: 'OK', caycStatus: CaycStatus.NonCompliant })
);

renderBranchOverview();
@@ -267,9 +267,27 @@ describe('application overview', () => {
it("should show projects that don't have a compliant quality gate", async () => {
const appStatus = mockQualityGateApplicationStatus({
projects: [
{ key: '1', name: 'first project', conditions: [], isCaycCompliant: false, status: 'OK' },
{ key: '2', name: 'second', conditions: [], isCaycCompliant: true, status: 'OK' },
{ key: '3', name: 'number 3', conditions: [], isCaycCompliant: false, status: 'OK' },
{
key: '1',
name: 'first project',
conditions: [],
caycStatus: CaycStatus.NonCompliant,
status: 'OK',
},
{
key: '2',
name: 'second',
conditions: [],
caycStatus: CaycStatus.Compliant,
status: 'OK',
},
{
key: '3',
name: 'number 3',
conditions: [],
caycStatus: CaycStatus.NonCompliant,
status: 'OK',
},
{
key: '4',
name: 'four',
@@ -282,7 +300,7 @@ describe('application overview', () => {
errorThreshold: '0',
},
],
isCaycCompliant: false,
caycStatus: CaycStatus.NonCompliant,
status: 'ERROR',
},
],

+ 3
- 1
server/sonar-web/src/main/js/apps/overview/branches/__tests__/QualityGatePanelSection-test.tsx View File

@@ -27,6 +27,7 @@ import {
} from '../../../../helpers/mocks/quality-gates';
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey } from '../../../../types/metrics';
import { CaycStatus } from '../../../../types/types';
import { QualityGatePanelSection, QualityGatePanelSectionProps } from '../QualityGatePanelSection';

it('should render correctly', () => {
@@ -36,6 +37,7 @@ it('should render correctly', () => {
qgStatus: mockQualityGateStatus({
failedConditions: [],
status: 'OK',
caycStatus: CaycStatus.Compliant,
}),
}).type()
).toBeNull();
@@ -55,7 +57,7 @@ function shallowRender(props: Partial<QualityGatePanelSectionProps> = {}) {
mockQualityGateStatusConditionEnhanced({ metric: MetricKey.new_bugs }),
],
status: 'ERROR',
isCaycCompliant: false,
caycStatus: CaycStatus.NonCompliant,
})}
{...props}
/>

+ 6
- 6
server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap View File

@@ -69,6 +69,7 @@ exports[`should render correctly for applications 1`] = `
key="foo"
qgStatus={
{
"caycStatus": "compliant",
"failedConditions": [
{
"actual": "10",
@@ -95,7 +96,6 @@ exports[`should render correctly for applications 1`] = `
},
],
"ignoredConditions": false,
"isCaycCompliant": true,
"key": "foo",
"name": "Foo",
"status": "ERROR",
@@ -128,6 +128,7 @@ exports[`should render correctly for applications 1`] = `
key="foo"
qgStatus={
{
"caycStatus": "compliant",
"failedConditions": [
{
"actual": "10",
@@ -177,7 +178,6 @@ exports[`should render correctly for applications 1`] = `
},
],
"ignoredConditions": false,
"isCaycCompliant": true,
"key": "foo",
"name": "Foo",
"status": "ERROR",
@@ -333,6 +333,7 @@ exports[`should render correctly for applications 2`] = `
key="foo"
qgStatus={
{
"caycStatus": "compliant",
"failedConditions": [
{
"actual": "10",
@@ -359,7 +360,6 @@ exports[`should render correctly for applications 2`] = `
},
],
"ignoredConditions": false,
"isCaycCompliant": true,
"key": "foo",
"name": "Foo",
"status": "ERROR",
@@ -392,9 +392,9 @@ exports[`should render correctly for applications 2`] = `
key="foo"
qgStatus={
{
"caycStatus": "compliant",
"failedConditions": [],
"ignoredConditions": false,
"isCaycCompliant": true,
"key": "foo",
"name": "Foo",
"status": "OK",
@@ -504,6 +504,7 @@ exports[`should render correctly for projects 1`] = `
key="foo"
qgStatus={
{
"caycStatus": "compliant",
"failedConditions": [
{
"actual": "10",
@@ -530,7 +531,6 @@ exports[`should render correctly for projects 1`] = `
},
],
"ignoredConditions": false,
"isCaycCompliant": true,
"key": "foo",
"name": "Foo",
"status": "ERROR",
@@ -702,6 +702,7 @@ exports[`should render correctly for projects 3`] = `
key="foo"
qgStatus={
{
"caycStatus": "compliant",
"failedConditions": [
{
"actual": "10",
@@ -728,7 +729,6 @@ exports[`should render correctly for projects 3`] = `
},
],
"ignoredConditions": true,
"isCaycCompliant": true,
"key": "foo",
"name": "Foo",
"status": "ERROR",

+ 4
- 4
server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanelSection-test.tsx.snap View File

@@ -40,6 +40,7 @@ exports[`should render correctly 1`] = `
<Memo(QualityGateConditions)
component={
{
"caycStatus": "non-compliant",
"failedConditions": [
{
"actual": "10",
@@ -89,7 +90,6 @@ exports[`should render correctly 1`] = `
},
],
"ignoredConditions": false,
"isCaycCompliant": false,
"key": "foo",
"name": "Foo",
"status": "ERROR",
@@ -131,6 +131,7 @@ exports[`should render correctly 1`] = `
<Memo(QualityGateConditions)
component={
{
"caycStatus": "non-compliant",
"failedConditions": [
{
"actual": "10",
@@ -180,7 +181,6 @@ exports[`should render correctly 1`] = `
},
],
"ignoredConditions": false,
"isCaycCompliant": false,
"key": "foo",
"name": "Foo",
"status": "ERROR",
@@ -251,6 +251,7 @@ exports[`should render correctly 2`] = `
<Memo(QualityGateConditions)
component={
{
"caycStatus": "non-compliant",
"failedConditions": [
{
"actual": "10",
@@ -300,7 +301,6 @@ exports[`should render correctly 2`] = `
},
],
"ignoredConditions": false,
"isCaycCompliant": false,
"key": "foo",
"name": "Foo",
"status": "ERROR",
@@ -342,6 +342,7 @@ exports[`should render correctly 2`] = `
<Memo(QualityGateConditions)
component={
{
"caycStatus": "non-compliant",
"failedConditions": [
{
"actual": "10",
@@ -391,7 +392,6 @@ exports[`should render correctly 2`] = `
},
],
"ignoredConditions": false,
"isCaycCompliant": false,
"key": "foo",
"name": "Foo",
"status": "ERROR",

+ 35
- 0
server/sonar-web/src/main/js/apps/quality-gates/components/CaycOverCompliantBadgeTooltip.tsx View File

@@ -0,0 +1,35 @@
/*
* 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 DocLink from '../../../components/common/DocLink';
import { translate } from '../../../helpers/l10n';

export default function CaycOverCompliantBadgeTooltip() {
return (
<div>
<p className="spacer-bottom padded-bottom bordered-bottom-cayc">
{translate('quality_gates.cayc_over_compliant.tooltip.message')}
</p>
<DocLink to="/user-guide/clean-as-you-code/#potential-drawbacks">
{translate('quality_gates.cayc_over_compliant.badge.tooltip.learn_more')}
</DocLink>
</div>
);
}

+ 36
- 7
server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx View File

@@ -21,11 +21,25 @@ import classNames from 'classnames';
import * as React from 'react';
import { deleteCondition } from '../../../api/quality-gates';
import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
import { colors } from '../../../app/theme';
import { DeleteButton, EditButton } from '../../../components/controls/buttons';
import ConfirmModal from '../../../components/controls/ConfirmModal';
import Tooltip from '../../../components/controls/Tooltip';
import InfoIcon from '../../../components/icons/InfoIcon';
import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n';
import { Condition as ConditionType, Dict, Metric, QualityGate } from '../../../types/types';
import { CAYC_CONDITIONS_WITHOUT_FIXED_VALUE, getLocalizedMetricNameNoDiffMetric } from '../utils';
import {
CaycStatus,
Condition as ConditionType,
Dict,
Metric,
QualityGate,
} from '../../../types/types';
import {
CAYC_CONDITIONS_WITH_FIXED_VALUE,
getLocalizedMetricNameNoDiffMetric,
isCaycCondition,
} from '../utils';
import CaycOverCompliantBadgeTooltip from './CaycOverCompliantBadgeTooltip';
import ConditionModal from './ConditionModal';
import ConditionValue from './ConditionValue';

@@ -47,6 +61,7 @@ interface State {
modal: boolean;
}

const TOOLTIP_MOUSE_LEAVE_DELAY = 0.3;
export class ConditionComponent extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
@@ -103,6 +118,8 @@ export class ConditionComponent extends React.PureComponent<Props, State> {
isCaycModal = false,
} = this.props;

const isCaycCompliantAndOverCompliant = qualityGate.caycStatus !== CaycStatus.NonCompliant;

return (
<tr className={classNames({ highlighted: updated })}>
<td className="text-middle">
@@ -119,15 +136,25 @@ export class ConditionComponent extends React.PureComponent<Props, State> {
metric={metric}
isCaycModal={isCaycModal}
condition={condition}
isCaycCompliant={qualityGate.isCaycCompliant}
isCaycCompliantAndOverCompliant={isCaycCompliantAndOverCompliant}
/>
</td>
<td className="text-middle nowrap display-flex-justify-end">
{!isCaycCondition(condition) && isCaycCompliantAndOverCompliant && (
<span className="display-flex-center spacer-right">
<Tooltip
overlay={<CaycOverCompliantBadgeTooltip />}
mouseLeaveDelay={TOOLTIP_MOUSE_LEAVE_DELAY}
>
<InfoIcon fill={colors.alertIconInfo} />
</Tooltip>
</span>
)}
{!isCaycModal && canEdit && (
<>
{(!qualityGate.isCaycCompliant ||
CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(condition.metric) ||
(qualityGate.isCaycCompliant && showEdit)) && (
{(!isCaycCompliantAndOverCompliant ||
!CAYC_CONDITIONS_WITH_FIXED_VALUE.includes(condition.metric) ||
(isCaycCompliantAndOverCompliant && showEdit)) && (
<>
<EditButton
aria-label={translateWithParameters(
@@ -150,7 +177,9 @@ export class ConditionComponent extends React.PureComponent<Props, State> {
)}
</>
)}
{(!qualityGate.isCaycCompliant || (qualityGate.isCaycCompliant && showEdit)) && (
{(!isCaycCompliantAndOverCompliant ||
!isCaycCondition(condition) ||
(isCaycCompliantAndOverCompliant && showEdit)) && (
<>
<DeleteButton
aria-label={translateWithParameters(

+ 50
- 30
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx View File

@@ -20,16 +20,12 @@
import { sortBy } from 'lodash';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { createCondition, deleteCondition, updateCondition } from '../../../api/quality-gates';
import { createCondition, updateCondition } from '../../../api/quality-gates';
import DocLink from '../../../components/common/DocLink';
import ConfirmModal from '../../../components/controls/ConfirmModal';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Condition, Dict, Metric, QualityGate } from '../../../types/types';
import {
getCaycConditionsWithCorrectValue,
getCorrectCaycCondition,
getWeakMissingAndNonCaycConditions,
} from '../utils';
import { getCorrectCaycCondition, getWeakMissingAndNonCaycConditions } from '../utils';
import ConditionsTable from './ConditionsTable';

interface Props {
@@ -50,8 +46,7 @@ export default class CaycReviewUpdateConditionsModal extends React.PureComponent
updateCaycQualityGate = () => {
const { conditions, qualityGate } = this.props;
const promiseArr: Promise<Condition | undefined | void>[] = [];
const { weakConditions, missingConditions, nonCaycConditions } =
getWeakMissingAndNonCaycConditions(conditions);
const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions);

weakConditions.forEach((condition) => {
promiseArr.push(
@@ -80,14 +75,6 @@ export default class CaycReviewUpdateConditionsModal extends React.PureComponent
);
});

nonCaycConditions.forEach((condition) => {
promiseArr.push(
deleteCondition({ id: condition.id })
.then(() => this.props.onRemoveCondition(condition))
.catch(() => undefined)
);
});

return Promise.all(promiseArr).then(() => {
this.props.lockEditing();
});
@@ -95,11 +82,18 @@ export default class CaycReviewUpdateConditionsModal extends React.PureComponent

render() {
const { conditions, qualityGate, metrics } = this.props;
const caycConditionsWithCorrectValue = getCaycConditionsWithCorrectValue(conditions);
const sortedConditions = sortBy(
caycConditionsWithCorrectValue,

const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions);
const sortedWeakConditions = sortBy(
weakConditions,
(condition) => metrics[condition.metric] && metrics[condition.metric].name
);

const sortedMissingConditions = sortBy(
missingConditions,
(condition) => metrics[condition.metric] && metrics[condition.metric].name
);

return (
<ConfirmModal
header={translateWithParameters(
@@ -125,18 +119,44 @@ export default class CaycReviewUpdateConditionsModal extends React.PureComponent
}}
/>
</p>
<p className="big-spacer-top big-spacer-bottom">

{sortedMissingConditions.length > 0 && (
<>
<h4 className="big-spacer-top spacer-bottom">
{translateWithParameters(
'quality_gates.cayc.review_update_modal.add_condition.header',
sortedMissingConditions.length
)}
</h4>
<ConditionsTable
{...this.props}
conditions={sortedMissingConditions}
showEdit={false}
isCaycModal={true}
/>
</>
)}

{sortedWeakConditions.length > 0 && (
<>
<h4 className="big-spacer-top spacer-bottom">
{translateWithParameters(
'quality_gates.cayc.review_update_modal.modify_condition.header',
sortedWeakConditions.length
)}
</h4>
<ConditionsTable
{...this.props}
conditions={sortedWeakConditions}
showEdit={false}
isCaycModal={true}
/>
</>
)}

<h4 className="big-spacer-top spacer-bottom">
{translate('quality_gates.cayc.review_update_modal.description2')}
</p>
<h3 className="medium text-normal spacer-top spacer-bottom">
{translate('quality_gates.conditions.new_code', 'long')}
</h3>
<ConditionsTable
{...this.props}
conditions={sortedConditions}
showEdit={false}
isCaycModal={true}
/>
</h4>
</div>
</ConfirmModal>
);

+ 22
- 5
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValue.tsx View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
import * as React from 'react';
import { formatMeasure } from '../../../helpers/measures';
import { Condition, Metric } from '../../../types/types';
@@ -27,17 +28,33 @@ interface Props {
condition: Condition;
isCaycModal?: boolean;
metric: Metric;
isCaycCompliant?: boolean;
isCaycCompliantAndOverCompliant?: boolean;
}

function ConditionValue({ condition, isCaycModal, metric, isCaycCompliant }: Props) {
function ConditionValue({
condition,
isCaycModal,
metric,
isCaycCompliantAndOverCompliant,
}: Props) {
if (isCaycModal) {
const isToBeModified = condition.error !== getCorrectCaycCondition(condition).error;

return (
<>
<span className="spacer-right">
{isToBeModified && (
<span className="red-text strike-through spacer-right">
{formatMeasure(condition.error, metric.type)}
</span>
)}
<span className={classNames('spacer-right', { 'green-text': isToBeModified })}>
{formatMeasure(getCorrectCaycCondition(condition).error, metric.type)}
</span>
<ConditionValueDescription condition={getCorrectCaycCondition(condition)} metric={metric} />
<ConditionValueDescription
className={classNames({ 'green-text': isToBeModified })}
condition={getCorrectCaycCondition(condition)}
metric={metric}
/>
</>
);
}
@@ -45,7 +62,7 @@ function ConditionValue({ condition, isCaycModal, metric, isCaycCompliant }: Pro
return (
<>
<span className="spacer-right">{formatMeasure(condition.error, metric.type)}</span>
{isCaycCompliant && isCaycCondition(condition) && (
{isCaycCompliantAndOverCompliant && isCaycCondition(condition) && (
<ConditionValueDescription condition={condition} metric={metric} />
)}
</>

+ 46
- 19
server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx View File

@@ -33,7 +33,13 @@ import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
import { isDiffMetric } from '../../../helpers/measures';
import { Feature } from '../../../types/features';
import { MetricKey } from '../../../types/metrics';
import { Condition as ConditionType, Dict, Metric, QualityGate } from '../../../types/types';
import {
CaycStatus,
Condition as ConditionType,
Dict,
Metric,
QualityGate,
} from '../../../types/types';
import ConditionModal from './ConditionModal';
import CaycReviewUpdateConditionsModal from './ConditionReviewAndUpdateModal';
import ConditionsTable from './ConditionsTable';
@@ -63,14 +69,14 @@ export class Conditions extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
unlockEditing: !props.qualityGate.isCaycCompliant,
unlockEditing: props.qualityGate.caycStatus === CaycStatus.NonCompliant,
};
}

componentDidUpdate(prevProps: Readonly<Props>): void {
const { qualityGate } = this.props;
if (prevProps.qualityGate.name !== qualityGate.name) {
this.setState({ unlockEditing: !qualityGate.isCaycCompliant });
this.setState({ unlockEditing: qualityGate.caycStatus === CaycStatus.NonCompliant });
}
}

@@ -164,7 +170,7 @@ export class Conditions extends React.PureComponent<Props, State> {

return (
<div className="quality-gate-section">
{qualityGate.isCaycCompliant && (
{qualityGate.caycStatus !== CaycStatus.NonCompliant && (
<Alert className="big-spacer-top big-spacer-bottom cayc-success-banner" variant="success">
<h4 className="spacer-bottom cayc-success-header">
{translate('quality_gates.cayc.banner.title')}
@@ -180,7 +186,7 @@ export class Conditions extends React.PureComponent<Props, State> {
</DocLink>
),
new_code_link: (
<DocLink to="/project-administration/defining-new-code//">
<DocLink to="/project-administration/defining-new-code/">
{translate('quality_gates.cayc.new_code')}
</DocLink>
),
@@ -197,7 +203,29 @@ export class Conditions extends React.PureComponent<Props, State> {
</Alert>
)}

{!qualityGate.isCaycCompliant && (
{qualityGate.caycStatus === CaycStatus.OverCompliant && (
<Alert className="big-spacer-top big-spacer-bottom cayc-success-banner" variant="info">
<h4 className="spacer-bottom cayc-over-compliant-header">
{translate('quality_gates.cayc_over_compliant.banner.title')}
</h4>
<p>{translate('quality_gates.cayc_over_compliant.banner.description1')}</p>
<div className="cayc-warning-description spacer-top">
<FormattedMessage
id="quality_gates.cayc_over_compliant.banner.description2"
defaultMessage={translate('quality_gates.cayc_over_compliant.banner.description2')}
values={{
link: (
<DocLink to="/user-guide/clean-as-you-code/#potential-drawbacks">
{translate('quality_gates.cayc_over_compliant.banner.link')}
</DocLink>
),
}}
/>
</div>
</Alert>
)}

{qualityGate.caycStatus === CaycStatus.NonCompliant && (
<Alert className="big-spacer-top big-spacer-bottom" variant="warning">
<h4 className="spacer-bottom cayc-warning-header">
{translate('quality_gates.cayc_missing.banner.title')}
@@ -227,18 +255,17 @@ export class Conditions extends React.PureComponent<Props, State> {
</Alert>
)}

{(!qualityGate.isCaycCompliant || (qualityGate.isCaycCompliant && unlockEditing)) &&
canEdit && (
<div className="pull-right">
<ModalButton modal={this.renderConditionModal}>
{({ onClick }) => (
<Button data-test="quality-gates__add-condition" onClick={onClick}>
{translate('quality_gates.add_condition')}
</Button>
)}
</ModalButton>
</div>
)}
{(qualityGate.caycStatus === CaycStatus.NonCompliant || unlockEditing) && canEdit && (
<div className="pull-right">
<ModalButton modal={this.renderConditionModal}>
{({ onClick }) => (
<Button data-test="quality-gates__add-condition" onClick={onClick}>
{translate('quality_gates.add_condition')}
</Button>
)}
</ModalButton>
</div>
)}

<header className="display-flex-center">
<h2 className="big">{translate('quality_gates.conditions')}</h2>
@@ -314,7 +341,7 @@ export class Conditions extends React.PureComponent<Props, State> {
</div>
)}

{qualityGate.isCaycCompliant && !unlockEditing && canEdit && (
{qualityGate.caycStatus !== CaycStatus.NonCompliant && !unlockEditing && canEdit && (
<div className="big-spacer-top big-spacer-bottom cayc-warning-description it__qg-unfollow-cayc">
<p>
<FormattedMessage

+ 3
- 3
server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx View File

@@ -86,7 +86,7 @@ export default class Details extends React.PureComponent<Props, State> {
addGlobalSuccessMessage(translate('quality_gates.condition_added'));

const updatedQualityGate = addCondition(clone(qualityGate), condition);
if (qualityGate.isCaycCompliant !== updatedQualityGate.isCaycCompliant) {
if (qualityGate.caycStatus !== updatedQualityGate.caycStatus) {
this.props.refreshQualityGates();
}

@@ -104,7 +104,7 @@ export default class Details extends React.PureComponent<Props, State> {
}
addGlobalSuccessMessage(translate('quality_gates.condition_updated'));
const updatedQualityGate = replaceCondition(clone(qualityGate), newCondition, oldCondition);
if (qualityGate.isCaycCompliant !== updatedQualityGate.isCaycCompliant) {
if (qualityGate.caycStatus !== updatedQualityGate.caycStatus) {
this.props.refreshQualityGates();
}
return {
@@ -121,7 +121,7 @@ export default class Details extends React.PureComponent<Props, State> {
}
addGlobalSuccessMessage(translate('quality_gates.condition_deleted'));
const updatedQualityGate = deleteCondition(clone(qualityGate), condition);
if (qualityGate.isCaycCompliant !== updatedQualityGate.isCaycCompliant) {
if (qualityGate.caycStatus !== updatedQualityGate.caycStatus) {
this.props.refreshQualityGates();
}
return {

+ 10
- 7
server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx View File

@@ -24,7 +24,7 @@ import ModalButton from '../../../components/controls/ModalButton';
import Tooltip from '../../../components/controls/Tooltip';
import AlertWarnIcon from '../../../components/icons/AlertWarnIcon';
import { translate } from '../../../helpers/l10n';
import { QualityGate } from '../../../types/types';
import { CaycStatus, QualityGate } from '../../../types/types';
import BuiltInQualityGateBadge from './BuiltInQualityGateBadge';
import CaycBadgeTooltip from './CaycBadgeTooltip';
import CopyQualityGateForm from './CopyQualityGateForm';
@@ -64,7 +64,6 @@ export default class DetailsHeader extends React.PureComponent<Props> {
render() {
const { qualityGate } = this.props;
const actions = qualityGate.actions || ({} as any);
const { isCaycCompliant } = qualityGate;

return (
<div className="layout-page-header-panel layout-page-main-header issues-main-header">
@@ -73,7 +72,7 @@ export default class DetailsHeader extends React.PureComponent<Props> {
<div className="pull-left display-flex-center">
<h2>{qualityGate.name}</h2>
{qualityGate.isBuiltIn && <BuiltInQualityGateBadge className="spacer-left" />}
{!qualityGate.isCaycCompliant && (
{qualityGate.caycStatus === CaycStatus.NonCompliant && (
<Tooltip overlay={<CaycBadgeTooltip />} mouseLeaveDelay={TOOLTIP_MOUSE_LEAVE_DELAY}>
<AlertWarnIcon className="spacer-left" />
</Tooltip>
@@ -111,7 +110,9 @@ export default class DetailsHeader extends React.PureComponent<Props> {
{({ onClick }) => (
<Tooltip
overlay={
!isCaycCompliant ? translate('quality_gates.cannot_copy_no_cayc') : null
qualityGate.caycStatus === CaycStatus.NonCompliant
? translate('quality_gates.cannot_copy_no_cayc')
: null
}
accessible={false}
>
@@ -119,7 +120,7 @@ export default class DetailsHeader extends React.PureComponent<Props> {
className="little-spacer-left"
id="quality-gate-copy"
onClick={onClick}
disabled={!isCaycCompliant}
disabled={qualityGate.caycStatus === CaycStatus.NonCompliant}
>
{translate('copy')}
</Button>
@@ -130,13 +131,15 @@ export default class DetailsHeader extends React.PureComponent<Props> {
{actions.setAsDefault && (
<Tooltip
overlay={
!isCaycCompliant ? translate('quality_gates.cannot_set_default_no_cayc') : null
qualityGate.caycStatus === CaycStatus.NonCompliant
? translate('quality_gates.cannot_set_default_no_cayc')
: null
}
accessible={false}
>
<Button
className="little-spacer-left"
disabled={!isCaycCompliant}
disabled={qualityGate.caycStatus === CaycStatus.NonCompliant}
id="quality-gate-toggle-default"
onClick={this.handleSetAsDefaultClick}
>

+ 2
- 2
server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx View File

@@ -23,7 +23,7 @@ import Tooltip from '../../../components/controls/Tooltip';
import AlertWarnIcon from '../../../components/icons/AlertWarnIcon';
import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls';
import { QualityGate } from '../../../types/types';
import { CaycStatus, QualityGate } from '../../../types/types';
import BuiltInQualityGateBadge from './BuiltInQualityGateBadge';

interface Props {
@@ -49,7 +49,7 @@ export default function List({ qualityGates, currentQualityGate }: Props) {
)}
{qualityGate.isBuiltIn && <BuiltInQualityGateBadge className="little-spacer-left" />}

{!qualityGate.isCaycCompliant && (
{qualityGate.caycStatus === CaycStatus.NonCompliant && (
<>
{/* Adding a11y-hidden span for accessibility */}
<span className="a11y-hidden">{translate('quality_gates.cayc.tooltip.message')}</span>

+ 25
- 1
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx View File

@@ -340,7 +340,10 @@ it('should show warning banner when CAYC condition is not properly set and shoul
expect(conditionsWrapper.getAllByText('Coverage')).toHaveLength(2); // This quality gate has duplicate condition
expect(conditionsWrapper.getByText('Duplicated Lines (%)')).toBeInTheDocument();

expect(screen.queryByTestId('quality-gates__conditions-overall')).not.toBeInTheDocument();
const overallConditionsWrapper = within(
await screen.findByTestId('quality-gates__conditions-overall')
);
expect(overallConditionsWrapper.getByText('Complexity / Function')).toBeInTheDocument();
});

it('should show success banner when quality gate is CAYC compliant', async () => {
@@ -371,6 +374,27 @@ it('should show success banner when quality gate is CAYC compliant', async () =>
expect(await conditionsWrapper.findByText('Duplicated Lines (%)')).toBeInTheDocument();
});

it('should show info banner when quality gate is CAYC over-compliant', async () => {
const user = userEvent.setup();
handler.setIsAdmin(true);
renderQualityGateApp();

const qualityGate = await screen.findByText('Over Compliant CAYC QG');

await user.click(qualityGate);

expect(screen.getByText('quality_gates.cayc.banner.title')).toBeInTheDocument();
expect(screen.getByText('quality_gates.cayc.banner.description')).toBeInTheDocument();
expect(screen.getByText('quality_gates.cayc_over_compliant.banner.title')).toBeInTheDocument();
expect(
screen.queryByText('quality_gates.cayc_condition.missing_warning.title')
).not.toBeInTheDocument();
expect(
screen.queryByRole('button', { name: 'quality_gates.cayc_condition.review_update' })
).not.toBeInTheDocument();
expect(screen.getByText('quality_gates.cayc.unlock_edit')).toBeInTheDocument();
});

it('should unlock editing option for CAYC conditions', async () => {
const user = userEvent.setup();
handler.setIsAdmin(true);

+ 16
- 0
server/sonar-web/src/main/js/apps/quality-gates/styles.css View File

@@ -73,6 +73,22 @@
color: var(--alertTextSuccess);
}

.cayc-over-compliant-header {
color: var(--veryDarkBlue);
}

.cayc-warning-description {
line-height: 18px;
}

.red-text {
color: var(--red);
}

.green-text {
color: var(--success500);
}

.strike-through {
text-decoration: line-through;
}

+ 20
- 18
server/sonar-web/src/main/js/apps/quality-gates/utils.ts View File

@@ -19,7 +19,7 @@
*/
import { getLocalizedMetricName } from '../../helpers/l10n';
import { isDiffMetric } from '../../helpers/measures';
import { Condition, Dict, Metric, QualityGate } from '../../types/types';
import { CaycStatus, Condition, Dict, Metric, QualityGate } from '../../types/types';

const CAYC_CONDITIONS: { [key: string]: Condition } = {
new_reliability_rating: {
@@ -61,6 +61,12 @@ const CAYC_CONDITIONS: { [key: string]: Condition } = {
};

export const CAYC_CONDITIONS_WITHOUT_FIXED_VALUE = ['new_duplicated_lines_density', 'new_coverage'];
export const CAYC_CONDITIONS_WITH_FIXED_VALUE = [
'new_security_hotspots_reviewed',
'new_maintainability_rating',
'new_security_rating',
'new_reliability_rating',
];

export function isCaycCondition(condition: Condition) {
return condition.metric in CAYC_CONDITIONS;
@@ -70,11 +76,9 @@ export function getWeakMissingAndNonCaycConditions(conditions: Condition[]) {
const result: {
weakConditions: Condition[];
missingConditions: Condition[];
nonCaycConditions: Condition[];
} = {
weakConditions: [],
missingConditions: [],
nonCaycConditions: [],
};
Object.keys(CAYC_CONDITIONS).forEach((key) => {
const selectedCondition = conditions.find((condition) => condition.metric === key);
@@ -87,8 +91,6 @@ export function getWeakMissingAndNonCaycConditions(conditions: Condition[]) {
result.weakConditions.push(selectedCondition);
}
});

result.nonCaycConditions = getNonCaycConditions(conditions);
return result;
}

@@ -102,10 +104,6 @@ export function getCaycConditionsWithCorrectValue(conditions: Condition[]) {
});
}

export function getNonCaycConditions(conditions: Condition[]) {
return conditions.filter((condition) => !isCaycCondition(condition));
}

export function getCorrectCaycCondition(condition: Condition) {
if (CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(condition.metric)) {
return condition;
@@ -121,7 +119,7 @@ export function addCondition(qualityGate: QualityGate, condition: Condition): Qu
const oldConditions = qualityGate.conditions || [];
const conditions = [...oldConditions, condition];
if (conditions) {
qualityGate.isCaycCompliant = updateCaycComplaintStatus(conditions);
qualityGate.caycStatus = updateCaycCompliantStatus(conditions);
}
return { ...qualityGate, conditions };
}
@@ -130,7 +128,7 @@ export function deleteCondition(qualityGate: QualityGate, condition: Condition):
const conditions =
qualityGate.conditions && qualityGate.conditions.filter((candidate) => candidate !== condition);
if (conditions) {
qualityGate.isCaycCompliant = updateCaycComplaintStatus(conditions);
qualityGate.caycStatus = updateCaycCompliantStatus(conditions);
}
return { ...qualityGate, conditions };
}
@@ -146,21 +144,21 @@ export function replaceCondition(
return candidate === oldCondition ? newCondition : candidate;
});
if (conditions) {
qualityGate.isCaycCompliant = updateCaycComplaintStatus(conditions);
qualityGate.caycStatus = updateCaycCompliantStatus(conditions);
}

return { ...qualityGate, conditions };
}

export function updateCaycComplaintStatus(conditions: Condition[]) {
if (conditions.length !== Object.keys(CAYC_CONDITIONS).length) {
return false;
export function updateCaycCompliantStatus(conditions: Condition[]) {
if (conditions.length < Object.keys(CAYC_CONDITIONS).length) {
return CaycStatus.NonCompliant;
}

for (const key of Object.keys(CAYC_CONDITIONS)) {
const selectedCondition = conditions.find((condition) => condition.metric === key);
if (!selectedCondition) {
return false;
return CaycStatus.NonCompliant;
}

if (
@@ -168,11 +166,15 @@ export function updateCaycComplaintStatus(conditions: Condition[]) {
selectedCondition &&
selectedCondition.error !== CAYC_CONDITIONS[key].error
) {
return false;
return CaycStatus.NonCompliant;
}
}

return true;
if (conditions.length > Object.keys(CAYC_CONDITIONS).length) {
return CaycStatus.OverCompliant;
}

return CaycStatus.Compliant;
}

export function getPossibleOperators(metric: Metric) {

+ 5
- 5
server/sonar-web/src/main/js/helpers/mocks/quality-gates.ts View File

@@ -24,7 +24,7 @@ import {
QualityGateStatusCondition,
QualityGateStatusConditionEnhanced,
} from '../../types/quality-gates';
import { QualityGate } from '../../types/types';
import { CaycStatus, QualityGate } from '../../types/types';
import { mockMeasureEnhanced, mockMetric } from '../testMocks';

export function mockQualityGate(overrides: Partial<QualityGate> = {}): QualityGate {
@@ -39,7 +39,7 @@ export function mockQualityGateStatus(
): QualityGateStatus {
return {
ignoredConditions: false,
isCaycCompliant: true,
caycStatus: CaycStatus.Compliant,
failedConditions: [mockQualityGateStatusConditionEnhanced()],
key: 'foo',
name: 'Foo',
@@ -90,7 +90,7 @@ export function mockQualityGateProjectStatus(
},
],
ignoredConditions: false,
isCaycCompliant: true,
caycStatus: CaycStatus.Compliant,
status: 'OK',
...overrides,
};
@@ -122,7 +122,7 @@ export function mockQualityGateApplicationStatus(
value: '5',
},
],
isCaycCompliant: true,
caycStatus: CaycStatus.Compliant,
status: 'ERROR',
},
{
@@ -138,7 +138,7 @@ export function mockQualityGateApplicationStatus(
value: '15',
},
],
isCaycCompliant: true,
caycStatus: CaycStatus.Compliant,
status: 'ERROR',
},
],

+ 4
- 4
server/sonar-web/src/main/js/types/quality-gates.ts View File

@@ -18,14 +18,14 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { BranchLike } from './branch-like';
import { MeasureEnhanced, Metric, Status } from './types';
import { CaycStatus, MeasureEnhanced, Metric, Status } from './types';
import { UserBase } from './users';

export interface QualityGateProjectStatus {
conditions?: QualityGateProjectStatusCondition[];
ignoredConditions: boolean;
status: Status;
isCaycCompliant: boolean;
caycStatus: CaycStatus;
}

export interface QualityGateProjectStatusCondition {
@@ -59,13 +59,13 @@ export interface QualityGateApplicationStatusChildProject {
key: string;
name: string;
status: Status;
isCaycCompliant: boolean;
caycStatus: CaycStatus;
}

export interface QualityGateStatus {
failedConditions: QualityGateStatusConditionEnhanced[];
ignoredConditions?: boolean;
isCaycCompliant: boolean;
caycStatus: CaycStatus;
key: string;
name: string;
status: Status;

+ 7
- 1
server/sonar-web/src/main/js/types/types.ts View File

@@ -506,6 +506,12 @@ export interface ProjectLink {
url: string;
}

export enum CaycStatus {
Compliant = 'compliant',
NonCompliant = 'non-compliant',
OverCompliant = 'over-compliant',
}

export interface QualityGate {
actions?: {
associateProjects?: boolean;
@@ -518,7 +524,7 @@ export interface QualityGate {
};
conditions?: Condition[];
isBuiltIn?: boolean;
isCaycCompliant?: boolean;
caycStatus?: CaycStatus;
isDefault?: boolean;
name: string;
}

+ 1
- 1
server/sonar-webserver-core/src/main/java/org/sonar/server/telemetry/TelemetryDataLoaderImpl.java View File

@@ -241,7 +241,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader {
for (QualityGateDto qualityGateDto : qualityGateDtos) {
qualityGates.add(
new TelemetryData.QualityGate(qualityGateDto.getUuid(), qualityGateCaycChecker.checkCaycCompliant(dbSession,
qualityGateDto.getUuid()))
qualityGateDto.getUuid()).toString())
);
}


+ 7
- 4
server/sonar-webserver-core/src/test/java/org/sonar/server/telemetry/TelemetryDataLoaderImplTest.java View File

@@ -63,6 +63,7 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.assertj.core.groups.Tuple.tuple;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -83,6 +84,7 @@ import static org.sonar.core.platform.EditionProvider.Edition.ENTERPRISE;
import static org.sonar.db.component.BranchType.BRANCH;
import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_CPP_KEY;
import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_C_KEY;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
import static org.sonar.server.telemetry.TelemetryDataLoaderImpl.SCIM_PROPERTY_ENABLED;

@RunWith(DataProviderRunner.class)
@@ -118,6 +120,7 @@ public class TelemetryDataLoaderImplTest {
public void setUpBuiltInQualityGate() {
String builtInQgName = "Sonar Way";
builtInDefaultQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName(builtInQgName).setBuiltIn(true));
when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(NON_COMPLIANT);
db.qualityGates().setDefaultQualityGate(builtInDefaultQualityGate);

bugsDto = db.measures().insertMetric(m -> m.setKey(BUGS_KEY));
@@ -229,11 +232,11 @@ public class TelemetryDataLoaderImplTest {
tuple(1L, 0L, qualityGate1.getUuid(), "scm-1", "ci-1", "azure_devops_cloud", Optional.of(1L), Optional.of(1L), Optional.of(1L), Optional.of(50L), Optional.of(5L)),
tuple(1L, 0L, builtInDefaultQualityGate.getUuid(), "scm-2", "ci-2", "github_cloud", Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()));
assertThat(data.getQualityGates())
.extracting(TelemetryData.QualityGate::uuid, TelemetryData.QualityGate::isCaycCompliant)
.extracting(TelemetryData.QualityGate::uuid, TelemetryData.QualityGate::caycStatus)
.containsExactlyInAnyOrder(
tuple(builtInDefaultQualityGate.getUuid(), false),
tuple(qualityGate1.getUuid(), false),
tuple(qualityGate2.getUuid(), false)
tuple(builtInDefaultQualityGate.getUuid(), "non-compliant"),
tuple(qualityGate1.getUuid(), "non-compliant"),
tuple(qualityGate2.getUuid(), "non-compliant")
);
}


+ 15
- 6
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateCaycChecker.java View File

@@ -40,6 +40,9 @@ import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING;
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED;
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.OVER_COMPLIANT;

public class QualityGateCaycChecker {

@@ -67,13 +70,13 @@ public class QualityGateCaycChecker {
this.dbClient = dbClient;
}

public boolean checkCaycCompliant(DbSession dbSession, String qualityGateUuid) {
public QualityGateCaycStatus checkCaycCompliant(DbSession dbSession, String qualityGateUuid) {
var conditionsByMetricId = dbClient.gateConditionDao().selectForQualityGate(dbSession, qualityGateUuid)
.stream()
.collect(uniqueIndex(QualityGateConditionDto::getMetricUuid));

if (conditionsByMetricId.size() != CAYC_METRICS.size()) {
return false;
if (conditionsByMetricId.size() < CAYC_METRICS.size()) {
return NON_COMPLIANT;
}

var metrics = dbClient.metricDao().selectByUuids(dbSession, conditionsByMetricId.keySet())
@@ -85,14 +88,20 @@ public class QualityGateCaycChecker {
.filter(metric -> checkMetricCaycCompliant(conditionsByMetricId.get(metric.getUuid()), metric))
.count();

return count == CAYC_METRICS.size();
if (metrics.size() == count && count == CAYC_METRICS.size()) {
return COMPLIANT;
} else if (metrics.size() > count && count == CAYC_METRICS.size()) {
return OVER_COMPLIANT;
}

return NON_COMPLIANT;
}

public boolean checkCaycCompliantFromProject(DbSession dbSession, String projectUuid) {
public QualityGateCaycStatus checkCaycCompliantFromProject(DbSession dbSession, String projectUuid) {
return Optional.ofNullable(dbClient.qualityGateDao().selectByProjectUuid(dbSession, projectUuid))
.or(() -> Optional.ofNullable(dbClient.qualityGateDao().selectDefault(dbSession)))
.map(qualityGate -> checkCaycCompliant(dbSession, qualityGate.getUuid()))
.orElse(false);
.orElse(NON_COMPLIANT);
}

private static boolean checkMetricCaycCompliant(QualityGateConditionDto condition, MetricDto metric) {

+ 38
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateCaycStatus.java View File

@@ -0,0 +1,38 @@
/*
* 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.
*/
package org.sonar.server.qualitygate;

public enum QualityGateCaycStatus {

NON_COMPLIANT("non-compliant"),
COMPLIANT("compliant"),
OVER_COMPLIANT("over-compliant");

final String status;

QualityGateCaycStatus(String status) {
this.status = status;
}

@Override
public String toString() {
return status;
}
}

+ 2
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/ListAction.java View File

@@ -59,7 +59,7 @@ public class ListAction implements QualityGatesWsAction {
.setSince("4.3")
.setResponseExample(Resources.getResource(this.getClass(), "list-example.json"))
.setChangelog(
new Change("9.9", "'isCaycCompliant' field is added on quality gate"),
new Change("9.9", "'caycStatus' field is added on quality gate"),
new Change("8.4", "Field 'id' in the response is deprecated. Format changes from integer to string."),
new Change("7.0", "'isDefault' field is added on quality gate"),
new Change("7.0", "'default' field on root level is deprecated"),
@@ -88,7 +88,7 @@ public class ListAction implements QualityGatesWsAction {
.setName(qualityGate.getName())
.setIsDefault(qualityGate.getUuid().equals(defaultUuid))
.setIsBuiltIn(qualityGate.isBuiltIn())
.setIsCaycCompliant(qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid()))
.setCaycStatus(qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid()).toString())
.setActions(wsSupport.getActions(dbSession, qualityGate, defaultQualityGate))
.build())
.collect(toList()));

+ 4
- 3
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/ProjectStatusAction.java View File

@@ -42,6 +42,7 @@ import org.sonar.db.project.ProjectDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.qualitygate.QualityGateCaycChecker;
import org.sonar.server.qualitygate.QualityGateCaycStatus;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.KeyExamples;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse;
@@ -95,7 +96,7 @@ public class ProjectStatusAction implements QualityGatesWsAction {
.setSince("5.3")
.setHandler(this)
.setChangelog(
new Change("9.9", "'isCaycCompliant' field is added to the response"),
new Change("9.9", "'caycStatus' field is added to the response"),
new Change("9.5", "The 'Execute Analysis' permission also allows to access the endpoint"),
new Change("8.5", "The field 'periods' in the response is deprecated. Use 'period' instead"),
new Change("7.7", "The parameters 'branch' and 'pullRequest' were added"),
@@ -152,10 +153,10 @@ public class ProjectStatusAction implements QualityGatesWsAction {
ProjectAndSnapshot projectAndSnapshot = getProjectAndSnapshot(dbSession, analysisId, projectUuid, projectKey, branchKey, pullRequestId);
checkPermission(projectAndSnapshot.project);
Optional<String> measureData = loadQualityGateDetails(dbSession, projectAndSnapshot, analysisId != null);
boolean isCaycCompliant = qualityGateCaycChecker.checkCaycCompliantFromProject(dbSession, projectAndSnapshot.project.getUuid());
QualityGateCaycStatus caycStatus = qualityGateCaycChecker.checkCaycCompliantFromProject(dbSession, projectAndSnapshot.project.getUuid());

return ProjectStatusResponse.newBuilder()
.setProjectStatus(new QualityGateDetailsFormatter(measureData.orElse(null), projectAndSnapshot.snapshotDto.orElse(null), isCaycCompliant).format())
.setProjectStatus(new QualityGateDetailsFormatter(measureData.orElse(null), projectAndSnapshot.snapshotDto.orElse(null), caycStatus).format())
.build();
}


+ 6
- 5
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java View File

@@ -28,6 +28,7 @@ import java.util.function.Predicate;
import java.util.stream.StreamSupport;
import javax.annotation.Nullable;
import org.sonar.db.component.SnapshotDto;
import org.sonar.server.qualitygate.QualityGateCaycStatus;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.NewCodePeriod;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Period;
@@ -38,13 +39,13 @@ import static org.sonar.api.utils.DateUtils.formatDateTime;
public class QualityGateDetailsFormatter {
private final Optional<String> optionalMeasureData;
private final Optional<SnapshotDto> optionalSnapshot;
private final boolean isCaycCompliant;
private final QualityGateCaycStatus caycStatus;
private final ProjectStatusResponse.ProjectStatus.Builder projectStatusBuilder;

public QualityGateDetailsFormatter(@Nullable String measureData, @Nullable SnapshotDto snapshot, boolean isCaycCompliant) {
public QualityGateDetailsFormatter(@Nullable String measureData, @Nullable SnapshotDto snapshot, QualityGateCaycStatus caycStatus) {
this.optionalMeasureData = Optional.ofNullable(measureData);
this.optionalSnapshot = Optional.ofNullable(snapshot);
this.isCaycCompliant = isCaycCompliant;
this.caycStatus = caycStatus;
this.projectStatusBuilder = ProjectStatusResponse.ProjectStatus.newBuilder();
}

@@ -57,7 +58,7 @@ public class QualityGateDetailsFormatter {

ProjectStatusResponse.Status qualityGateStatus = measureLevelToQualityGateStatus(json.get("level").getAsString());
projectStatusBuilder.setStatus(qualityGateStatus);
projectStatusBuilder.setIsCaycCompliant(isCaycCompliant);
projectStatusBuilder.setCaycStatus(caycStatus.toString());

formatIgnoredConditions(json);
formatConditions(json.getAsJsonArray("conditions"));
@@ -204,7 +205,7 @@ public class QualityGateDetailsFormatter {
}

private ProjectStatusResponse.ProjectStatus newResponseWithoutQualityGateDetails() {
return ProjectStatusResponse.ProjectStatus.newBuilder().setStatus(ProjectStatusResponse.Status.NONE).setIsCaycCompliant(isCaycCompliant).build();
return ProjectStatusResponse.ProjectStatus.newBuilder().setStatus(ProjectStatusResponse.Status.NONE).setCaycStatus(caycStatus.toString()).build();
}

private static Predicate<JsonObject> isConditionOnValidPeriod() {

+ 6
- 5
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/ShowAction.java View File

@@ -35,6 +35,7 @@ import org.sonar.db.metric.MetricDto;
import org.sonar.db.qualitygate.QualityGateConditionDto;
import org.sonar.db.qualitygate.QualityGateDto;
import org.sonar.server.qualitygate.QualityGateCaycChecker;
import org.sonar.server.qualitygate.QualityGateCaycStatus;
import org.sonar.server.qualitygate.QualityGateFinder;
import org.sonarqube.ws.Qualitygates.ShowWsResponse;

@@ -71,7 +72,7 @@ public class ShowAction implements QualityGatesWsAction {
.setSince("4.3")
.setResponseExample(Resources.getResource(this.getClass(), "show-example.json"))
.setChangelog(
new Change("9.9", "'isCaycCompliant' field is added to the response"),
new Change("9.9", "'caycStatus' field is added to the response"),
new Change("8.4", "Parameter 'id' is deprecated. Format changes from integer to string. Use 'name' instead."),
new Change("8.4", "Field 'id' in the response is deprecated."),
new Change("7.6", "'period' and 'warning' fields of conditions are removed from the response"),
@@ -99,8 +100,8 @@ public class ShowAction implements QualityGatesWsAction {
Collection<QualityGateConditionDto> conditions = getConditions(dbSession, qualityGate);
Map<String, MetricDto> metricsByUuid = getMetricsByUuid(dbSession, conditions);
QualityGateDto defaultQualityGate = qualityGateFinder.getDefault(dbSession);
boolean isCaycCompliant = qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid());
writeProtobuf(buildResponse(dbSession, qualityGate, defaultQualityGate, conditions, metricsByUuid, isCaycCompliant), request, response);
QualityGateCaycStatus caycStatus = qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid());
writeProtobuf(buildResponse(dbSession, qualityGate, defaultQualityGate, conditions, metricsByUuid, caycStatus), request, response);
}
}

@@ -124,12 +125,12 @@ public class ShowAction implements QualityGatesWsAction {
}

private ShowWsResponse buildResponse(DbSession dbSession, QualityGateDto qualityGate, QualityGateDto defaultQualityGate, Collection<QualityGateConditionDto> conditions,
Map<String, MetricDto> metricsByUuid, boolean isCaycCompliant) {
Map<String, MetricDto> metricsByUuid, QualityGateCaycStatus caycStatus) {
return ShowWsResponse.newBuilder()
.setId(qualityGate.getUuid())
.setName(qualityGate.getName())
.setIsBuiltIn(qualityGate.isBuiltIn())
.setIsCaycCompliant(isCaycCompliant)
.setCaycStatus(caycStatus.toString())
.addAllConditions(conditions.stream()
.map(toWsCondition(metricsByUuid))
.collect(toList()))

+ 1
- 1
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/project_status-example.json View File

@@ -2,7 +2,7 @@
"projectStatus": {
"status": "ERROR",
"ignoredConditions": false,
"isCaycCompliant": false,
"caycStatus": "non-compliant",
"conditions": [
{
"status": "ERROR",

+ 9
- 6
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateCaycCheckerTest.java View File

@@ -30,7 +30,7 @@ import org.sonar.db.DbTester;
import org.sonar.db.metric.MetricDto;
import org.sonar.db.qualitygate.QualityGateConditionDto;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.sonar.api.measures.CoreMetrics.DUPLICATED_LINES;
import static org.sonar.api.measures.CoreMetrics.LINE_COVERAGE;
import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE;
@@ -40,6 +40,9 @@ import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING;
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED;
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING;
import static org.sonar.server.qualitygate.QualityGateCaycChecker.CAYC_METRICS;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.OVER_COMPLIANT;

public class QualityGateCaycCheckerTest {

@@ -51,7 +54,7 @@ public class QualityGateCaycCheckerTest {
public void checkCaycCompliant() {
String qualityGateUuid = "abcd";
CAYC_METRICS.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue()));
assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isTrue();
assertEquals(COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid));
}

@Test
@@ -62,7 +65,7 @@ public class QualityGateCaycCheckerTest {
// extra conditions outside of CAYC requirements
List.of(LINE_COVERAGE, DUPLICATED_LINES).forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue()));

assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isFalse();
assertEquals(OVER_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid));
}

@Test
@@ -75,7 +78,7 @@ public class QualityGateCaycCheckerTest {
var metric = metrics.get(i);
insertCondition(metric, qualityGateUuid, idx == i ? metric.getWorstValue() : metric.getBestValue());
}
assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isFalse();
assertEquals(NON_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid));
});
}

@@ -84,7 +87,7 @@ public class QualityGateCaycCheckerTest {
String qualityGateUuid = "abcd";
List.of(NEW_MAINTAINABILITY_RATING, NEW_RELIABILITY_RATING, NEW_SECURITY_HOTSPOTS_REVIEWED, NEW_DUPLICATED_LINES_DENSITY)
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue()));
assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isFalse();
assertEquals(NON_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid));
}

@Test
@@ -94,7 +97,7 @@ public class QualityGateCaycCheckerTest {
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue()));
List.of(NEW_COVERAGE, NEW_DUPLICATED_LINES_DENSITY)
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getWorstValue()));
assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isTrue();
assertEquals(COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid));
}

private void insertCondition(MetricDto metricDto, String qualityGateUuid, Double threshold) {

+ 18
- 6
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/ListActionTest.java View File

@@ -19,6 +19,7 @@
*/
package org.sonar.server.qualitygate.ws;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
@@ -43,6 +44,9 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES;
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.OVER_COMPLIANT;
import static org.sonar.test.JsonAssert.assertJson;
import static org.sonarqube.ws.Qualitygates.ListWsResponse;

@@ -61,6 +65,11 @@ public class ListActionTest {
private final WsActionTester ws = new WsActionTester(new ListAction(db.getDbClient(),
new QualityGatesWsSupport(dbClient, userSession, TestComponentFinder.from(db)), qualityGateFinder, qualityGateCaycChecker));

@Before
public void setUp() {
when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(COMPLIANT);
}

@Test
public void list_quality_gates() {
QualityGateDto defaultQualityGate = db.qualityGates().insertQualityGate();
@@ -94,11 +103,13 @@ public class ListActionTest {
}

@Test
public void test_isCaycCompliant_flag() {
public void test_caycStatus_flag() {
QualityGateDto qualityGate1 = db.qualityGates().insertQualityGate();
QualityGateDto qualityGate2 = db.qualityGates().insertQualityGate();
when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate1.getUuid()))).thenReturn(true);
when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate2.getUuid()))).thenReturn(false);
QualityGateDto qualityGate3 = db.qualityGates().insertQualityGate();
when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate1.getUuid()))).thenReturn(COMPLIANT);
when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate2.getUuid()))).thenReturn(NON_COMPLIANT);
when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate3.getUuid()))).thenReturn(OVER_COMPLIANT);

db.qualityGates().setDefaultQualityGate(qualityGate1);

@@ -106,10 +117,11 @@ public class ListActionTest {
.executeProtobuf(ListWsResponse.class);

assertThat(response.getQualitygatesList())
.extracting(QualityGate::getId, QualityGate::getIsCaycCompliant)
.extracting(QualityGate::getId, QualityGate::getCaycStatus)
.containsExactlyInAnyOrder(
tuple(qualityGate1.getUuid(), true),
tuple(qualityGate2.getUuid(), false));
tuple(qualityGate1.getUuid(), COMPLIANT.toString()),
tuple(qualityGate2.getUuid(), NON_COMPLIANT.toString()),
tuple(qualityGate3.getUuid(), OVER_COMPLIANT.toString()));
}

@Test

+ 11
- 3
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/ProjectStatusActionTest.java View File

@@ -23,6 +23,7 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.measures.CoreMetrics;
@@ -37,7 +38,6 @@ import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.metric.MetricDto;
import org.sonar.db.permission.GlobalPermission;
import org.sonar.db.qualitygate.QualityGateDto;
import org.sonar.server.component.TestComponentFinder;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
@@ -53,6 +53,7 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.tuple;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -61,6 +62,8 @@ import static org.sonar.db.component.SnapshotTesting.newAnalysis;
import static org.sonar.db.measure.MeasureTesting.newLiveMeasure;
import static org.sonar.db.measure.MeasureTesting.newMeasureDto;
import static org.sonar.db.metric.MetricTesting.newMetricDto;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_ANALYSIS_ID;
import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_BRANCH;
import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_PROJECT_ID;
@@ -82,6 +85,11 @@ public class ProjectStatusActionTest {

private final WsActionTester ws = new WsActionTester(new ProjectStatusAction(dbClient, TestComponentFinder.from(db), userSession, qualityGateCaycChecker));

@Before
public void setUp() {
when(qualityGateCaycChecker.checkCaycCompliantFromProject(any(), any())).thenReturn(NON_COMPLIANT);
}

@Test
public void test_definition() {
WebService.Action action = ws.getDef();
@@ -328,7 +336,7 @@ public class ProjectStatusActionTest {
ComponentDto project = db.components().insertPrivateProject();
var qg = db.qualityGates().insertBuiltInQualityGate();
db.qualityGates().setDefaultQualityGate(qg);
when(qualityGateCaycChecker.checkCaycCompliantFromProject(any(DbSession.class), eq(project.uuid()))).thenReturn(true);
when(qualityGateCaycChecker.checkCaycCompliantFromProject(any(DbSession.class), eq(project.uuid()))).thenReturn(COMPLIANT);
SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(project));
dbSession.commit();
userSession.addProjectPermission(UserRole.USER, project);
@@ -337,7 +345,7 @@ public class ProjectStatusActionTest {
.setParam(PARAM_ANALYSIS_ID, snapshot.getUuid())
.executeProtobuf(ProjectStatusResponse.class);

assertThat(result.getProjectStatus().getIsCaycCompliant()).isTrue();
assertEquals(COMPLIANT.toString(), result.getProjectStatus().getCaycStatus());
}

@Test

+ 4
- 3
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatterTest.java View File

@@ -21,7 +21,6 @@ package org.sonar.server.qualitygate.ws;

import java.io.IOException;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
@@ -31,7 +30,9 @@ import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.ProjectStatus;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;

public class QualityGateDetailsFormatterTest {

@@ -49,7 +50,7 @@ public class QualityGateDetailsFormatterTest {
ProjectStatus result = underTest.format();

assertThat(result.getStatus()).isEqualTo(ProjectStatusResponse.Status.ERROR);
assertThat(result.getIsCaycCompliant()).isFalse();
assertEquals(NON_COMPLIANT.toString(), result.getCaycStatus());
// check conditions
assertThat(result.getConditionsCount()).isEqualTo(3);
List<ProjectStatusResponse.Condition> conditions = result.getConditionsList();
@@ -145,6 +146,6 @@ public class QualityGateDetailsFormatterTest {
}

private static QualityGateDetailsFormatter newQualityGateDetailsFormatter(@Nullable String measureData, @Nullable SnapshotDto snapshotDto) {
return new QualityGateDetailsFormatter(measureData, snapshotDto, false);
return new QualityGateDetailsFormatter(measureData, snapshotDto, NON_COMPLIANT);
}
}

+ 10
- 2
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/ShowActionTest.java View File

@@ -19,6 +19,7 @@
*/
package org.sonar.server.qualitygate.ws;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
@@ -43,12 +44,14 @@ import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForClassTypes.tuple;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES;
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES;
import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT;
import static org.sonar.test.JsonAssert.assertJson;
import static org.sonarqube.ws.Qualitygates.Actions;

@@ -64,6 +67,11 @@ public class ShowActionTest {
new ShowAction(db.getDbClient(), new QualityGateFinder(db.getDbClient()),
new QualityGatesWsSupport(db.getDbClient(), userSession, TestComponentFinder.from(db)), qualityGateCaycChecker));

@Before
public void setUp() {
when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(COMPLIANT);
}

@Test
public void show() {
QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
@@ -103,14 +111,14 @@ public class ShowActionTest {
@Test
public void show_isCaycCompliant() {
QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate.getUuid()))).thenReturn(true);
when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate.getUuid()))).thenReturn(COMPLIANT);
db.qualityGates().setDefaultQualityGate(qualityGate);

ShowWsResponse response = ws.newRequest()
.setParam("name", qualityGate.getName())
.executeProtobuf(ShowWsResponse.class);

assertThat(response.getIsCaycCompliant()).isTrue();
assertEquals(COMPLIANT.toString(), response.getCaycStatus());
}

@Test

+ 18
- 4
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -1849,8 +1849,8 @@ quality_gates.cayc_missing.banner.description={cayc_link} is the most efficient
quality_gates.cayc_condition.review_update=Review and Fix Quality Gate
quality_gates.cayc.review_update_modal.header=Fix "{0}" to comply with Clean as You Code
quality_gates.cayc.review_update_modal.confirm_text=Fix Quality Gate
quality_gates.cayc.review_update_modal.description1=This quality gate will be updated to comply with {cayc_link}. Existing conditions on duplication and coverage on new code will be preserved. All non-compliant conditions will be deleted, inlcuding conditions on overall code.
quality_gates.cayc.review_update_modal.description2=Please review the new quality gate conditions below.
quality_gates.cayc.review_update_modal.description1=This quality gate will be updated to comply with {cayc_link}. Please review the changes below.
quality_gates.cayc.review_update_modal.description2=All other conditions will be preserved
quality_gates.cayc.new_maintainability_rating.A=Technical debt ratio is less than {0}
quality_gates.cayc.new_maintainability_rating=Technical debt ratio is greater than {1}
quality_gates.cayc.new_reliability_rating.A=No bugs
@@ -1858,6 +1858,8 @@ quality_gates.cayc.new_security_rating.A=No vulnerabilities
quality_gates.cayc.unlock_edit=Unlock editing
quality_gates.cayc.tooltip.message=This quality gate does not comply with Clean as You Code.
quality_gates.cayc.badge.tooltip.learn_more=Learn more: Clean as You Code
quality_gates.cayc_over_compliant.tooltip.message=Additional conditions may have potential drawbacks.
quality_gates.cayc_over_compliant.badge.tooltip.learn_more=Learn more: Potential drawbacks
quality_gates.cayc.banner.title=This quality gate complies with Clean as You Code
quality_gates.cayc.banner.description=This quality gate complies with the {cayc_link} methodology, so that you benefit from the most efficient approach to delivering Clean Code. The quality gate ensures that the {new_code_link} you’re working on has:
quality_gates.cayc.banner.list_item1=No bugs
@@ -1865,7 +1867,13 @@ quality_gates.cayc.banner.list_item2=No vulnerabilities
quality_gates.cayc.banner.list_item3=No security hotspots to review
quality_gates.cayc.banner.list_item4=A limited amount of code smells (i.e. low technical debt ratio)
quality_gates.cayc.banner.list_item5=A controlled level of duplication and coverage
quality_gates.cayc_unfollow.description=Are you reconsidering {cayc_link}? We strongly recommend this methodology to achieve a Clean Code state. However, if you still wish to move out of this approach you may edit this quality gate.
quality_gates.cayc_over_compliant.banner.title=This quality gate may have drawbacks
quality_gates.cayc_over_compliant.banner.description1=This quality gate includes additional conditions that may have drawbacks.
quality_gates.cayc_over_compliant.banner.description2=Learn more: {link}
quality_gates.cayc_over_compliant.banner.link=Potential drawbacks
quality_gates.cayc_unfollow.description=You may click unlock to edit this quality gate. Adding extra conditions to a compliant quality gate can result in drawbacks. Are you reconsidering {cayc_link}? We strongly recommend this methodology to achieve a Clean Code status.
quality_gates.cayc.review_update_modal.add_condition.header= {0} condition(s) on New Code will be added
quality_gates.cayc.review_update_modal.modify_condition.header= {0} condition(s) on New Code will be modified

#------------------------------------------------------------------------------
#
@@ -3265,9 +3273,15 @@ overview.quality_gate.ignored_conditions.tooltip=At the start of a new code peri
overview.quality_gate.conditions_on_new_code=Only conditions on new code that are defined in the Quality Gate are checked. See the {link} associated to the project for details.
overview.quality_gate.conditions.cayc.warning=The quality gate used by this project does not comply with Clean as You Code.
overview.quality_gate.conditions.cayc.details=Fixing this quality gate will help you achieve a Clean Code state.
overview.quality_gate.conditions.cayc.review=Review Quality Gate
overview.quality_gate.conditions.cayc.details_with_link=Fixing {link} will help you achieve a Clean Code state.
overview.quality_gate.conditions.non_cayc.warning.link=this quality gate
overview.quality_gate.conditions.cayc_over_compliant.warning.link=This quality gate
overview.quality_gate.conditions.cayc_over_compliant.details=This quality gate is Clean as You Code compliant, but it includes additional conditions that may have drawbacks.
overview.quality_gate.conditions.cayc_over_compliant.details_with_link={link} is Clean as You Code compliant, but it includes additional conditions that may have drawbacks.
overview.quality_gate.conditions.cayc.link=Learn more: Clean as You Code
overview.quality_gate.conditions.cayc_over_compliant.link=Learn more: Potential drawbacks
overview.quality_gate.application.non_cayc.projects_x={0} project(s) in this application use a Quality Gate that does not comply with Clean as You Code
overview.quality_gate.application.cayc_over_compliant.projects_x={0} project(s) in this application use a Clean as You Code compliant quality gate with extra conditions. This may result in drawbacks.
overview.quality_gate.show_project_conditions_x=Show failed conditions for project {0}
overview.quality_gate.hide_project_conditions_x=Hide failed conditions for project {0}
overview.quality_profiles=Quality Profiles used

+ 3
- 3
sonar-ws/src/main/protobuf/ws-qualitygates.proto View File

@@ -36,7 +36,7 @@ message ProjectStatusResponse {
repeated Period periods = 3;
optional bool ignoredConditions = 4;
optional NewCodePeriod period = 5;
optional bool isCaycCompliant = 6;
optional string caycStatus = 6;
}

message Condition {
@@ -131,7 +131,7 @@ message ShowWsResponse {
repeated Condition conditions = 3;
optional bool isBuiltIn = 4;
optional Actions actions = 5;
optional bool isCaycCompliant = 6;
optional string caycStatus = 6;

message Condition {
optional string id = 1;
@@ -168,7 +168,7 @@ message ListWsResponse {
optional bool isDefault = 3;
optional bool isBuiltIn = 4;
optional Actions actions = 5;
optional bool isCaycCompliant = 6;
optional string caycStatus = 6;
}

message RootActions {

Loading…
Cancel
Save