record Project(String projectUuid, Long lastAnalysis, String language, Long loc) { | 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 { | public static class ProjectStatistics { |
statistics.getQualityGates().forEach(qualityGate -> { | statistics.getQualityGates().forEach(qualityGate -> { | ||||
json.beginObject(); | json.beginObject(); | ||||
json.prop("uuid", qualityGate.uuid()); | json.prop("uuid", qualityGate.uuid()); | ||||
json.prop("isCaycCompliant", qualityGate.isCaycCompliant()); | |||||
json.prop("caycStatus", qualityGate.caycStatus()); | |||||
json.endObject(); | json.endObject(); | ||||
}); | }); | ||||
json.endArray(); | json.endArray(); |
"quality-gates": [ | "quality-gates": [ | ||||
{ | { | ||||
"uuid": "uuid-0", | "uuid": "uuid-0", | ||||
"isCaycCompliant": true | |||||
"caycStatus": "non-compliant" | |||||
}, | }, | ||||
{ | { | ||||
"uuid": "uuid-1", | "uuid": "uuid-1", | ||||
"isCaycCompliant": false | |||||
"caycStatus": "compliant" | |||||
}, | }, | ||||
{ | { | ||||
"uuid": "uuid-2", | "uuid": "uuid-2", | ||||
"isCaycCompliant": true | |||||
"caycStatus": "over-compliant" | |||||
} | } | ||||
] | ] | ||||
} | } | ||||
} | } | ||||
private List<TelemetryData.QualityGate> attachQualityGates() { | 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 | @DataProvider |
import { mockUserBase } from '../../helpers/mocks/users'; | import { mockUserBase } from '../../helpers/mocks/users'; | ||||
import { mockCondition, mockGroup } from '../../helpers/testMocks'; | import { mockCondition, mockGroup } from '../../helpers/testMocks'; | ||||
import { MetricKey } from '../../types/metrics'; | import { MetricKey } from '../../types/metrics'; | ||||
import { Condition, QualityGate } from '../../types/types'; | |||||
import { CaycStatus, Condition, QualityGate } from '../../types/types'; | |||||
import { | import { | ||||
addGroup, | addGroup, | ||||
addUser, | addUser, | ||||
], | ], | ||||
isDefault: true, | isDefault: true, | ||||
isBuiltIn: false, | isBuiltIn: false, | ||||
isCaycCompliant: true, | |||||
caycStatus: CaycStatus.Compliant, | |||||
}), | }), | ||||
mockQualityGate({ | mockQualityGate({ | ||||
name: 'SonarSource way - CFamily', | name: 'SonarSource way - CFamily', | ||||
], | ], | ||||
isDefault: false, | isDefault: false, | ||||
isBuiltIn: false, | isBuiltIn: false, | ||||
caycStatus: CaycStatus.NonCompliant, | |||||
}), | }), | ||||
mockQualityGate({ | mockQualityGate({ | ||||
name: 'Sonar way', | name: 'Sonar way', | ||||
], | ], | ||||
isDefault: false, | isDefault: false, | ||||
isBuiltIn: true, | isBuiltIn: true, | ||||
isCaycCompliant: true, | |||||
caycStatus: CaycStatus.Compliant, | |||||
}), | }), | ||||
mockQualityGate({ | mockQualityGate({ | ||||
name: 'Non Cayc QG', | name: 'Non Cayc QG', | ||||
], | ], | ||||
isDefault: false, | isDefault: false, | ||||
isBuiltIn: 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({ | mockQualityGate({ | ||||
name: 'QG without conditions', | name: 'QG without conditions', | ||||
conditions: [], | conditions: [], | ||||
isDefault: false, | isDefault: false, | ||||
isBuiltIn: false, | isBuiltIn: false, | ||||
isCaycCompliant: false, | |||||
caycStatus: CaycStatus.NonCompliant, | |||||
}), | }), | ||||
mockQualityGate({ | mockQualityGate({ | ||||
name: 'QG without new code conditions', | name: 'QG without new code conditions', | ||||
], | ], | ||||
isDefault: false, | isDefault: false, | ||||
isBuiltIn: false, | isBuiltIn: false, | ||||
isCaycCompliant: false, | |||||
caycStatus: CaycStatus.NonCompliant, | |||||
}), | }), | ||||
]; | ]; | ||||
], | ], | ||||
isDefault: false, | isDefault: false, | ||||
isBuiltIn: false, | isBuiltIn: false, | ||||
isCaycCompliant: true, | |||||
caycStatus: CaycStatus.Compliant, | |||||
}) | }) | ||||
); | ); | ||||
return this.reply({ | return this.reply({ |
/* | |||||
* 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> | |||||
); | |||||
} |
if (this.mounted) { | if (this.mounted) { | ||||
const qgStatuses = results | const qgStatuses = results | ||||
.map(({ measures = [], project, projectBranchLike }) => { | .map(({ measures = [], project, projectBranchLike }) => { | ||||
const { key, name, status, isCaycCompliant } = project; | |||||
const { key, name, status, caycStatus } = project; | |||||
const conditions = extractStatusConditionsFromApplicationStatusChildProject(project); | const conditions = extractStatusConditionsFromApplicationStatusChildProject(project); | ||||
const failedConditions = this.getFailedConditions(conditions, measures); | const failedConditions = this.getFailedConditions(conditions, measures); | ||||
return { | return { | ||||
failedConditions, | failedConditions, | ||||
isCaycCompliant, | |||||
caycStatus, | |||||
key, | key, | ||||
name, | name, | ||||
status, | status, | ||||
this.loadMeasuresAndMeta(key, branch, metricKeys).then( | this.loadMeasuresAndMeta(key, branch, metricKeys).then( | ||||
({ measures, metrics, period }) => { | ({ measures, metrics, period }) => { | ||||
if (this.mounted && measures) { | if (this.mounted && measures) { | ||||
const { ignoredConditions, isCaycCompliant, status } = projectStatus; | |||||
const { ignoredConditions, caycStatus, status } = projectStatus; | |||||
const conditions = extractStatusConditionsFromProjectStatus(projectStatus); | const conditions = extractStatusConditionsFromProjectStatus(projectStatus); | ||||
const failedConditions = this.getFailedConditions(conditions, measures); | const failedConditions = this.getFailedConditions(conditions, measures); | ||||
const qgStatus = { | const qgStatus = { | ||||
ignoredConditions, | ignoredConditions, | ||||
isCaycCompliant, | |||||
caycStatus, | |||||
failedConditions, | failedConditions, | ||||
key, | key, | ||||
name, | name, |
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | |||||
import DocLink from '../../../components/common/DocLink'; | import DocLink from '../../../components/common/DocLink'; | ||||
import Link from '../../../components/common/Link'; | import Link from '../../../components/common/Link'; | ||||
import { Alert } from '../../../components/ui/Alert'; | import { Alert } from '../../../components/ui/Alert'; | ||||
return ( | return ( | ||||
<> | <> | ||||
<Alert variant="warning">{translate('overview.quality_gate.conditions.cayc.warning')}</Alert> | <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/"> | <DocLink to="/user-guide/clean-as-you-code/"> |
/* | |||||
* 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> | |||||
</> | |||||
); | |||||
} |
import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
import { flatMap } from 'lodash'; | import { flatMap } from 'lodash'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import Link from '../../../components/common/Link'; | |||||
import HelpTooltip from '../../../components/controls/HelpTooltip'; | import HelpTooltip from '../../../components/controls/HelpTooltip'; | ||||
import QualifierIcon from '../../../components/icons/QualifierIcon'; | |||||
import { Alert } from '../../../components/ui/Alert'; | import { Alert } from '../../../components/ui/Alert'; | ||||
import DeferredSpinner from '../../../components/ui/DeferredSpinner'; | import DeferredSpinner from '../../../components/ui/DeferredSpinner'; | ||||
import { getBranchLikeQuery } from '../../../helpers/branch-like'; | |||||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | import { translate, translateWithParameters } from '../../../helpers/l10n'; | ||||
import { getProjectQueryUrl } from '../../../helpers/urls'; | |||||
import { ComponentQualifier, isApplication } from '../../../types/component'; | import { ComponentQualifier, isApplication } from '../../../types/component'; | ||||
import { QualityGateStatus } from '../../../types/quality-gates'; | import { QualityGateStatus } from '../../../types/quality-gates'; | ||||
import { Component } from '../../../types/types'; | |||||
import { CaycStatus, Component } from '../../../types/types'; | |||||
import SonarLintPromotion from '../components/SonarLintPromotion'; | import SonarLintPromotion from '../components/SonarLintPromotion'; | ||||
import ApplicationNonCaycProjectWarning from './ApplicationNonCaycProjectWarning'; | |||||
import QualityGatePanelSection from './QualityGatePanelSection'; | import QualityGatePanelSection from './QualityGatePanelSection'; | ||||
export interface QualityGatePanelProps { | export interface QualityGatePanelProps { | ||||
const nonCaycProjectsInApp = isApplication(component.qualifier) | const nonCaycProjectsInApp = isApplication(component.qualifier) | ||||
? qgStatuses | ? 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' })) | .sort(({ name: a }, { name: b }) => a.localeCompare(b, undefined, { sensitivity: 'base' })) | ||||
: []; | : []; | ||||
</div> | </div> | ||||
{(overallFailedConditionsCount > 0 || | {(overallFailedConditionsCount > 0 || | ||||
qgStatuses.some(({ isCaycCompliant }) => !isCaycCompliant)) && ( | |||||
qgStatuses.some(({ caycStatus }) => caycStatus !== CaycStatus.Compliant)) && ( | |||||
<div data-test="overview__quality-gate-conditions"> | <div data-test="overview__quality-gate-conditions"> | ||||
{qgStatuses.map((qgStatus) => ( | {qgStatuses.map((qgStatus) => ( | ||||
<QualityGatePanelSection | <QualityGatePanelSection | ||||
)} | )} | ||||
{nonCaycProjectsInApp.length > 0 && ( | {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} | |||||
/> | |||||
)} | )} | ||||
</> | </> | ||||
)} | )} |
QualityGateStatus, | QualityGateStatus, | ||||
QualityGateStatusConditionEnhanced, | QualityGateStatusConditionEnhanced, | ||||
} from '../../../types/quality-gates'; | } from '../../../types/quality-gates'; | ||||
import { Component } from '../../../types/types'; | |||||
import { CaycStatus, Component } from '../../../types/types'; | |||||
import QualityGateConditions from '../components/QualityGateConditions'; | import QualityGateConditions from '../components/QualityGateConditions'; | ||||
import CleanAsYouCodeWarning from './CleanAsYouCodeWarning'; | import CleanAsYouCodeWarning from './CleanAsYouCodeWarning'; | ||||
import CleanAsYouCodeWarningOverCompliant from './CleanAsYouCodeWarningOverCompliant'; | |||||
export interface QualityGatePanelSectionProps { | export interface QualityGatePanelSectionProps { | ||||
branchLike?: BranchLike; | branchLike?: BranchLike; | ||||
if ( | if ( | ||||
!( | !( | ||||
qgStatus.failedConditions.length > 0 || | qgStatus.failedConditions.length > 0 || | ||||
(!qgStatus.isCaycCompliant && !isApplication(component.qualifier)) | |||||
(qgStatus.caycStatus !== CaycStatus.Compliant && !isApplication(component.qualifier)) | |||||
) | ) | ||||
) { | ) { | ||||
return null; | return null; | ||||
const showSectionTitles = | const showSectionTitles = | ||||
isApplication(component.qualifier) || | isApplication(component.qualifier) || | ||||
!qgStatus.isCaycCompliant || | |||||
qgStatus.caycStatus !== CaycStatus.Compliant || | |||||
(overallFailedConditions.length > 0 && newCodeFailedConditions.length > 0); | (overallFailedConditions.length > 0 && newCodeFailedConditions.length > 0); | ||||
const toggleLabel = collapsed | const toggleLabel = collapsed | ||||
{!collapsed && ( | {!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 && ( | {newCodeFailedConditions.length > 0 && ( | ||||
<> | <> |
import { ComponentQualifier } from '../../../../types/component'; | import { ComponentQualifier } from '../../../../types/component'; | ||||
import { MetricKey } from '../../../../types/metrics'; | import { MetricKey } from '../../../../types/metrics'; | ||||
import { GraphType } from '../../../../types/project-activity'; | 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'; | import BranchOverview, { BRANCH_OVERVIEW_ACTIVITY_GRAPH, NO_CI_DETECTED } from '../BranchOverview'; | ||||
jest.mock('../../../../api/measures', () => { | jest.mock('../../../../api/measures', () => { | ||||
jest | jest | ||||
.mocked(getQualityGateProjectStatus) | .mocked(getQualityGateProjectStatus) | ||||
.mockResolvedValueOnce( | .mockResolvedValueOnce( | ||||
mockQualityGateProjectStatus({ status: 'OK', isCaycCompliant: false }) | |||||
mockQualityGateProjectStatus({ status: 'OK', caycStatus: CaycStatus.NonCompliant }) | |||||
); | ); | ||||
renderBranchOverview(); | renderBranchOverview(); | ||||
it("should show projects that don't have a compliant quality gate", async () => { | it("should show projects that don't have a compliant quality gate", async () => { | ||||
const appStatus = mockQualityGateApplicationStatus({ | const appStatus = mockQualityGateApplicationStatus({ | ||||
projects: [ | 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', | key: '4', | ||||
name: 'four', | name: 'four', | ||||
errorThreshold: '0', | errorThreshold: '0', | ||||
}, | }, | ||||
], | ], | ||||
isCaycCompliant: false, | |||||
caycStatus: CaycStatus.NonCompliant, | |||||
status: 'ERROR', | status: 'ERROR', | ||||
}, | }, | ||||
], | ], |
} from '../../../../helpers/mocks/quality-gates'; | } from '../../../../helpers/mocks/quality-gates'; | ||||
import { ComponentQualifier } from '../../../../types/component'; | import { ComponentQualifier } from '../../../../types/component'; | ||||
import { MetricKey } from '../../../../types/metrics'; | import { MetricKey } from '../../../../types/metrics'; | ||||
import { CaycStatus } from '../../../../types/types'; | |||||
import { QualityGatePanelSection, QualityGatePanelSectionProps } from '../QualityGatePanelSection'; | import { QualityGatePanelSection, QualityGatePanelSectionProps } from '../QualityGatePanelSection'; | ||||
it('should render correctly', () => { | it('should render correctly', () => { | ||||
qgStatus: mockQualityGateStatus({ | qgStatus: mockQualityGateStatus({ | ||||
failedConditions: [], | failedConditions: [], | ||||
status: 'OK', | status: 'OK', | ||||
caycStatus: CaycStatus.Compliant, | |||||
}), | }), | ||||
}).type() | }).type() | ||||
).toBeNull(); | ).toBeNull(); | ||||
mockQualityGateStatusConditionEnhanced({ metric: MetricKey.new_bugs }), | mockQualityGateStatusConditionEnhanced({ metric: MetricKey.new_bugs }), | ||||
], | ], | ||||
status: 'ERROR', | status: 'ERROR', | ||||
isCaycCompliant: false, | |||||
caycStatus: CaycStatus.NonCompliant, | |||||
})} | })} | ||||
{...props} | {...props} | ||||
/> | /> |
key="foo" | key="foo" | ||||
qgStatus={ | qgStatus={ | ||||
{ | { | ||||
"caycStatus": "compliant", | |||||
"failedConditions": [ | "failedConditions": [ | ||||
{ | { | ||||
"actual": "10", | "actual": "10", | ||||
}, | }, | ||||
], | ], | ||||
"ignoredConditions": false, | "ignoredConditions": false, | ||||
"isCaycCompliant": true, | |||||
"key": "foo", | "key": "foo", | ||||
"name": "Foo", | "name": "Foo", | ||||
"status": "ERROR", | "status": "ERROR", | ||||
key="foo" | key="foo" | ||||
qgStatus={ | qgStatus={ | ||||
{ | { | ||||
"caycStatus": "compliant", | |||||
"failedConditions": [ | "failedConditions": [ | ||||
{ | { | ||||
"actual": "10", | "actual": "10", | ||||
}, | }, | ||||
], | ], | ||||
"ignoredConditions": false, | "ignoredConditions": false, | ||||
"isCaycCompliant": true, | |||||
"key": "foo", | "key": "foo", | ||||
"name": "Foo", | "name": "Foo", | ||||
"status": "ERROR", | "status": "ERROR", | ||||
key="foo" | key="foo" | ||||
qgStatus={ | qgStatus={ | ||||
{ | { | ||||
"caycStatus": "compliant", | |||||
"failedConditions": [ | "failedConditions": [ | ||||
{ | { | ||||
"actual": "10", | "actual": "10", | ||||
}, | }, | ||||
], | ], | ||||
"ignoredConditions": false, | "ignoredConditions": false, | ||||
"isCaycCompliant": true, | |||||
"key": "foo", | "key": "foo", | ||||
"name": "Foo", | "name": "Foo", | ||||
"status": "ERROR", | "status": "ERROR", | ||||
key="foo" | key="foo" | ||||
qgStatus={ | qgStatus={ | ||||
{ | { | ||||
"caycStatus": "compliant", | |||||
"failedConditions": [], | "failedConditions": [], | ||||
"ignoredConditions": false, | "ignoredConditions": false, | ||||
"isCaycCompliant": true, | |||||
"key": "foo", | "key": "foo", | ||||
"name": "Foo", | "name": "Foo", | ||||
"status": "OK", | "status": "OK", | ||||
key="foo" | key="foo" | ||||
qgStatus={ | qgStatus={ | ||||
{ | { | ||||
"caycStatus": "compliant", | |||||
"failedConditions": [ | "failedConditions": [ | ||||
{ | { | ||||
"actual": "10", | "actual": "10", | ||||
}, | }, | ||||
], | ], | ||||
"ignoredConditions": false, | "ignoredConditions": false, | ||||
"isCaycCompliant": true, | |||||
"key": "foo", | "key": "foo", | ||||
"name": "Foo", | "name": "Foo", | ||||
"status": "ERROR", | "status": "ERROR", | ||||
key="foo" | key="foo" | ||||
qgStatus={ | qgStatus={ | ||||
{ | { | ||||
"caycStatus": "compliant", | |||||
"failedConditions": [ | "failedConditions": [ | ||||
{ | { | ||||
"actual": "10", | "actual": "10", | ||||
}, | }, | ||||
], | ], | ||||
"ignoredConditions": true, | "ignoredConditions": true, | ||||
"isCaycCompliant": true, | |||||
"key": "foo", | "key": "foo", | ||||
"name": "Foo", | "name": "Foo", | ||||
"status": "ERROR", | "status": "ERROR", |
<Memo(QualityGateConditions) | <Memo(QualityGateConditions) | ||||
component={ | component={ | ||||
{ | { | ||||
"caycStatus": "non-compliant", | |||||
"failedConditions": [ | "failedConditions": [ | ||||
{ | { | ||||
"actual": "10", | "actual": "10", | ||||
}, | }, | ||||
], | ], | ||||
"ignoredConditions": false, | "ignoredConditions": false, | ||||
"isCaycCompliant": false, | |||||
"key": "foo", | "key": "foo", | ||||
"name": "Foo", | "name": "Foo", | ||||
"status": "ERROR", | "status": "ERROR", | ||||
<Memo(QualityGateConditions) | <Memo(QualityGateConditions) | ||||
component={ | component={ | ||||
{ | { | ||||
"caycStatus": "non-compliant", | |||||
"failedConditions": [ | "failedConditions": [ | ||||
{ | { | ||||
"actual": "10", | "actual": "10", | ||||
}, | }, | ||||
], | ], | ||||
"ignoredConditions": false, | "ignoredConditions": false, | ||||
"isCaycCompliant": false, | |||||
"key": "foo", | "key": "foo", | ||||
"name": "Foo", | "name": "Foo", | ||||
"status": "ERROR", | "status": "ERROR", | ||||
<Memo(QualityGateConditions) | <Memo(QualityGateConditions) | ||||
component={ | component={ | ||||
{ | { | ||||
"caycStatus": "non-compliant", | |||||
"failedConditions": [ | "failedConditions": [ | ||||
{ | { | ||||
"actual": "10", | "actual": "10", | ||||
}, | }, | ||||
], | ], | ||||
"ignoredConditions": false, | "ignoredConditions": false, | ||||
"isCaycCompliant": false, | |||||
"key": "foo", | "key": "foo", | ||||
"name": "Foo", | "name": "Foo", | ||||
"status": "ERROR", | "status": "ERROR", | ||||
<Memo(QualityGateConditions) | <Memo(QualityGateConditions) | ||||
component={ | component={ | ||||
{ | { | ||||
"caycStatus": "non-compliant", | |||||
"failedConditions": [ | "failedConditions": [ | ||||
{ | { | ||||
"actual": "10", | "actual": "10", | ||||
}, | }, | ||||
], | ], | ||||
"ignoredConditions": false, | "ignoredConditions": false, | ||||
"isCaycCompliant": false, | |||||
"key": "foo", | "key": "foo", | ||||
"name": "Foo", | "name": "Foo", | ||||
"status": "ERROR", | "status": "ERROR", |
/* | |||||
* 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> | |||||
); | |||||
} |
import * as React from 'react'; | import * as React from 'react'; | ||||
import { deleteCondition } from '../../../api/quality-gates'; | import { deleteCondition } from '../../../api/quality-gates'; | ||||
import withMetricsContext from '../../../app/components/metrics/withMetricsContext'; | import withMetricsContext from '../../../app/components/metrics/withMetricsContext'; | ||||
import { colors } from '../../../app/theme'; | |||||
import { DeleteButton, EditButton } from '../../../components/controls/buttons'; | import { DeleteButton, EditButton } from '../../../components/controls/buttons'; | ||||
import ConfirmModal from '../../../components/controls/ConfirmModal'; | 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 { 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 ConditionModal from './ConditionModal'; | ||||
import ConditionValue from './ConditionValue'; | import ConditionValue from './ConditionValue'; | ||||
modal: boolean; | modal: boolean; | ||||
} | } | ||||
const TOOLTIP_MOUSE_LEAVE_DELAY = 0.3; | |||||
export class ConditionComponent extends React.PureComponent<Props, State> { | export class ConditionComponent extends React.PureComponent<Props, State> { | ||||
constructor(props: Props) { | constructor(props: Props) { | ||||
super(props); | super(props); | ||||
isCaycModal = false, | isCaycModal = false, | ||||
} = this.props; | } = this.props; | ||||
const isCaycCompliantAndOverCompliant = qualityGate.caycStatus !== CaycStatus.NonCompliant; | |||||
return ( | return ( | ||||
<tr className={classNames({ highlighted: updated })}> | <tr className={classNames({ highlighted: updated })}> | ||||
<td className="text-middle"> | <td className="text-middle"> | ||||
metric={metric} | metric={metric} | ||||
isCaycModal={isCaycModal} | isCaycModal={isCaycModal} | ||||
condition={condition} | condition={condition} | ||||
isCaycCompliant={qualityGate.isCaycCompliant} | |||||
isCaycCompliantAndOverCompliant={isCaycCompliantAndOverCompliant} | |||||
/> | /> | ||||
</td> | </td> | ||||
<td className="text-middle nowrap display-flex-justify-end"> | <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 && ( | {!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 | <EditButton | ||||
aria-label={translateWithParameters( | aria-label={translateWithParameters( | ||||
)} | )} | ||||
</> | </> | ||||
)} | )} | ||||
{(!qualityGate.isCaycCompliant || (qualityGate.isCaycCompliant && showEdit)) && ( | |||||
{(!isCaycCompliantAndOverCompliant || | |||||
!isCaycCondition(condition) || | |||||
(isCaycCompliantAndOverCompliant && showEdit)) && ( | |||||
<> | <> | ||||
<DeleteButton | <DeleteButton | ||||
aria-label={translateWithParameters( | aria-label={translateWithParameters( |
import { sortBy } from 'lodash'; | import { sortBy } from 'lodash'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | 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 DocLink from '../../../components/common/DocLink'; | ||||
import ConfirmModal from '../../../components/controls/ConfirmModal'; | import ConfirmModal from '../../../components/controls/ConfirmModal'; | ||||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | import { translate, translateWithParameters } from '../../../helpers/l10n'; | ||||
import { Condition, Dict, Metric, QualityGate } from '../../../types/types'; | import { Condition, Dict, Metric, QualityGate } from '../../../types/types'; | ||||
import { | |||||
getCaycConditionsWithCorrectValue, | |||||
getCorrectCaycCondition, | |||||
getWeakMissingAndNonCaycConditions, | |||||
} from '../utils'; | |||||
import { getCorrectCaycCondition, getWeakMissingAndNonCaycConditions } from '../utils'; | |||||
import ConditionsTable from './ConditionsTable'; | import ConditionsTable from './ConditionsTable'; | ||||
interface Props { | interface Props { | ||||
updateCaycQualityGate = () => { | updateCaycQualityGate = () => { | ||||
const { conditions, qualityGate } = this.props; | const { conditions, qualityGate } = this.props; | ||||
const promiseArr: Promise<Condition | undefined | void>[] = []; | const promiseArr: Promise<Condition | undefined | void>[] = []; | ||||
const { weakConditions, missingConditions, nonCaycConditions } = | |||||
getWeakMissingAndNonCaycConditions(conditions); | |||||
const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions); | |||||
weakConditions.forEach((condition) => { | weakConditions.forEach((condition) => { | ||||
promiseArr.push( | promiseArr.push( | ||||
); | ); | ||||
}); | }); | ||||
nonCaycConditions.forEach((condition) => { | |||||
promiseArr.push( | |||||
deleteCondition({ id: condition.id }) | |||||
.then(() => this.props.onRemoveCondition(condition)) | |||||
.catch(() => undefined) | |||||
); | |||||
}); | |||||
return Promise.all(promiseArr).then(() => { | return Promise.all(promiseArr).then(() => { | ||||
this.props.lockEditing(); | this.props.lockEditing(); | ||||
}); | }); | ||||
render() { | render() { | ||||
const { conditions, qualityGate, metrics } = this.props; | 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 | (condition) => metrics[condition.metric] && metrics[condition.metric].name | ||||
); | ); | ||||
return ( | return ( | ||||
<ConfirmModal | <ConfirmModal | ||||
header={translateWithParameters( | header={translateWithParameters( | ||||
}} | }} | ||||
/> | /> | ||||
</p> | </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')} | {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> | </div> | ||||
</ConfirmModal> | </ConfirmModal> | ||||
); | ); |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import classNames from 'classnames'; | |||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { formatMeasure } from '../../../helpers/measures'; | import { formatMeasure } from '../../../helpers/measures'; | ||||
import { Condition, Metric } from '../../../types/types'; | import { Condition, Metric } from '../../../types/types'; | ||||
condition: Condition; | condition: Condition; | ||||
isCaycModal?: boolean; | isCaycModal?: boolean; | ||||
metric: Metric; | metric: Metric; | ||||
isCaycCompliant?: boolean; | |||||
isCaycCompliantAndOverCompliant?: boolean; | |||||
} | } | ||||
function ConditionValue({ condition, isCaycModal, metric, isCaycCompliant }: Props) { | |||||
function ConditionValue({ | |||||
condition, | |||||
isCaycModal, | |||||
metric, | |||||
isCaycCompliantAndOverCompliant, | |||||
}: Props) { | |||||
if (isCaycModal) { | if (isCaycModal) { | ||||
const isToBeModified = condition.error !== getCorrectCaycCondition(condition).error; | |||||
return ( | 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)} | {formatMeasure(getCorrectCaycCondition(condition).error, metric.type)} | ||||
</span> | </span> | ||||
<ConditionValueDescription condition={getCorrectCaycCondition(condition)} metric={metric} /> | |||||
<ConditionValueDescription | |||||
className={classNames({ 'green-text': isToBeModified })} | |||||
condition={getCorrectCaycCondition(condition)} | |||||
metric={metric} | |||||
/> | |||||
</> | </> | ||||
); | ); | ||||
} | } | ||||
return ( | return ( | ||||
<> | <> | ||||
<span className="spacer-right">{formatMeasure(condition.error, metric.type)}</span> | <span className="spacer-right">{formatMeasure(condition.error, metric.type)}</span> | ||||
{isCaycCompliant && isCaycCondition(condition) && ( | |||||
{isCaycCompliantAndOverCompliant && isCaycCondition(condition) && ( | |||||
<ConditionValueDescription condition={condition} metric={metric} /> | <ConditionValueDescription condition={condition} metric={metric} /> | ||||
)} | )} | ||||
</> | </> |
import { isDiffMetric } from '../../../helpers/measures'; | import { isDiffMetric } from '../../../helpers/measures'; | ||||
import { Feature } from '../../../types/features'; | import { Feature } from '../../../types/features'; | ||||
import { MetricKey } from '../../../types/metrics'; | 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 ConditionModal from './ConditionModal'; | ||||
import CaycReviewUpdateConditionsModal from './ConditionReviewAndUpdateModal'; | import CaycReviewUpdateConditionsModal from './ConditionReviewAndUpdateModal'; | ||||
import ConditionsTable from './ConditionsTable'; | import ConditionsTable from './ConditionsTable'; | ||||
constructor(props: Props) { | constructor(props: Props) { | ||||
super(props); | super(props); | ||||
this.state = { | this.state = { | ||||
unlockEditing: !props.qualityGate.isCaycCompliant, | |||||
unlockEditing: props.qualityGate.caycStatus === CaycStatus.NonCompliant, | |||||
}; | }; | ||||
} | } | ||||
componentDidUpdate(prevProps: Readonly<Props>): void { | componentDidUpdate(prevProps: Readonly<Props>): void { | ||||
const { qualityGate } = this.props; | const { qualityGate } = this.props; | ||||
if (prevProps.qualityGate.name !== qualityGate.name) { | if (prevProps.qualityGate.name !== qualityGate.name) { | ||||
this.setState({ unlockEditing: !qualityGate.isCaycCompliant }); | |||||
this.setState({ unlockEditing: qualityGate.caycStatus === CaycStatus.NonCompliant }); | |||||
} | } | ||||
} | } | ||||
return ( | return ( | ||||
<div className="quality-gate-section"> | <div className="quality-gate-section"> | ||||
{qualityGate.isCaycCompliant && ( | |||||
{qualityGate.caycStatus !== CaycStatus.NonCompliant && ( | |||||
<Alert className="big-spacer-top big-spacer-bottom cayc-success-banner" variant="success"> | <Alert className="big-spacer-top big-spacer-bottom cayc-success-banner" variant="success"> | ||||
<h4 className="spacer-bottom cayc-success-header"> | <h4 className="spacer-bottom cayc-success-header"> | ||||
{translate('quality_gates.cayc.banner.title')} | {translate('quality_gates.cayc.banner.title')} | ||||
</DocLink> | </DocLink> | ||||
), | ), | ||||
new_code_link: ( | new_code_link: ( | ||||
<DocLink to="/project-administration/defining-new-code//"> | |||||
<DocLink to="/project-administration/defining-new-code/"> | |||||
{translate('quality_gates.cayc.new_code')} | {translate('quality_gates.cayc.new_code')} | ||||
</DocLink> | </DocLink> | ||||
), | ), | ||||
</Alert> | </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"> | <Alert className="big-spacer-top big-spacer-bottom" variant="warning"> | ||||
<h4 className="spacer-bottom cayc-warning-header"> | <h4 className="spacer-bottom cayc-warning-header"> | ||||
{translate('quality_gates.cayc_missing.banner.title')} | {translate('quality_gates.cayc_missing.banner.title')} | ||||
</Alert> | </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"> | <header className="display-flex-center"> | ||||
<h2 className="big">{translate('quality_gates.conditions')}</h2> | <h2 className="big">{translate('quality_gates.conditions')}</h2> | ||||
</div> | </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"> | <div className="big-spacer-top big-spacer-bottom cayc-warning-description it__qg-unfollow-cayc"> | ||||
<p> | <p> | ||||
<FormattedMessage | <FormattedMessage |
addGlobalSuccessMessage(translate('quality_gates.condition_added')); | addGlobalSuccessMessage(translate('quality_gates.condition_added')); | ||||
const updatedQualityGate = addCondition(clone(qualityGate), condition); | const updatedQualityGate = addCondition(clone(qualityGate), condition); | ||||
if (qualityGate.isCaycCompliant !== updatedQualityGate.isCaycCompliant) { | |||||
if (qualityGate.caycStatus !== updatedQualityGate.caycStatus) { | |||||
this.props.refreshQualityGates(); | this.props.refreshQualityGates(); | ||||
} | } | ||||
} | } | ||||
addGlobalSuccessMessage(translate('quality_gates.condition_updated')); | addGlobalSuccessMessage(translate('quality_gates.condition_updated')); | ||||
const updatedQualityGate = replaceCondition(clone(qualityGate), newCondition, oldCondition); | const updatedQualityGate = replaceCondition(clone(qualityGate), newCondition, oldCondition); | ||||
if (qualityGate.isCaycCompliant !== updatedQualityGate.isCaycCompliant) { | |||||
if (qualityGate.caycStatus !== updatedQualityGate.caycStatus) { | |||||
this.props.refreshQualityGates(); | this.props.refreshQualityGates(); | ||||
} | } | ||||
return { | return { | ||||
} | } | ||||
addGlobalSuccessMessage(translate('quality_gates.condition_deleted')); | addGlobalSuccessMessage(translate('quality_gates.condition_deleted')); | ||||
const updatedQualityGate = deleteCondition(clone(qualityGate), condition); | const updatedQualityGate = deleteCondition(clone(qualityGate), condition); | ||||
if (qualityGate.isCaycCompliant !== updatedQualityGate.isCaycCompliant) { | |||||
if (qualityGate.caycStatus !== updatedQualityGate.caycStatus) { | |||||
this.props.refreshQualityGates(); | this.props.refreshQualityGates(); | ||||
} | } | ||||
return { | return { |
import Tooltip from '../../../components/controls/Tooltip'; | import Tooltip from '../../../components/controls/Tooltip'; | ||||
import AlertWarnIcon from '../../../components/icons/AlertWarnIcon'; | import AlertWarnIcon from '../../../components/icons/AlertWarnIcon'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { QualityGate } from '../../../types/types'; | |||||
import { CaycStatus, QualityGate } from '../../../types/types'; | |||||
import BuiltInQualityGateBadge from './BuiltInQualityGateBadge'; | import BuiltInQualityGateBadge from './BuiltInQualityGateBadge'; | ||||
import CaycBadgeTooltip from './CaycBadgeTooltip'; | import CaycBadgeTooltip from './CaycBadgeTooltip'; | ||||
import CopyQualityGateForm from './CopyQualityGateForm'; | import CopyQualityGateForm from './CopyQualityGateForm'; | ||||
render() { | render() { | ||||
const { qualityGate } = this.props; | const { qualityGate } = this.props; | ||||
const actions = qualityGate.actions || ({} as any); | const actions = qualityGate.actions || ({} as any); | ||||
const { isCaycCompliant } = qualityGate; | |||||
return ( | return ( | ||||
<div className="layout-page-header-panel layout-page-main-header issues-main-header"> | <div className="layout-page-header-panel layout-page-main-header issues-main-header"> | ||||
<div className="pull-left display-flex-center"> | <div className="pull-left display-flex-center"> | ||||
<h2>{qualityGate.name}</h2> | <h2>{qualityGate.name}</h2> | ||||
{qualityGate.isBuiltIn && <BuiltInQualityGateBadge className="spacer-left" />} | {qualityGate.isBuiltIn && <BuiltInQualityGateBadge className="spacer-left" />} | ||||
{!qualityGate.isCaycCompliant && ( | |||||
{qualityGate.caycStatus === CaycStatus.NonCompliant && ( | |||||
<Tooltip overlay={<CaycBadgeTooltip />} mouseLeaveDelay={TOOLTIP_MOUSE_LEAVE_DELAY}> | <Tooltip overlay={<CaycBadgeTooltip />} mouseLeaveDelay={TOOLTIP_MOUSE_LEAVE_DELAY}> | ||||
<AlertWarnIcon className="spacer-left" /> | <AlertWarnIcon className="spacer-left" /> | ||||
</Tooltip> | </Tooltip> | ||||
{({ onClick }) => ( | {({ onClick }) => ( | ||||
<Tooltip | <Tooltip | ||||
overlay={ | overlay={ | ||||
!isCaycCompliant ? translate('quality_gates.cannot_copy_no_cayc') : null | |||||
qualityGate.caycStatus === CaycStatus.NonCompliant | |||||
? translate('quality_gates.cannot_copy_no_cayc') | |||||
: null | |||||
} | } | ||||
accessible={false} | accessible={false} | ||||
> | > | ||||
className="little-spacer-left" | className="little-spacer-left" | ||||
id="quality-gate-copy" | id="quality-gate-copy" | ||||
onClick={onClick} | onClick={onClick} | ||||
disabled={!isCaycCompliant} | |||||
disabled={qualityGate.caycStatus === CaycStatus.NonCompliant} | |||||
> | > | ||||
{translate('copy')} | {translate('copy')} | ||||
</Button> | </Button> | ||||
{actions.setAsDefault && ( | {actions.setAsDefault && ( | ||||
<Tooltip | <Tooltip | ||||
overlay={ | 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} | accessible={false} | ||||
> | > | ||||
<Button | <Button | ||||
className="little-spacer-left" | className="little-spacer-left" | ||||
disabled={!isCaycCompliant} | |||||
disabled={qualityGate.caycStatus === CaycStatus.NonCompliant} | |||||
id="quality-gate-toggle-default" | id="quality-gate-toggle-default" | ||||
onClick={this.handleSetAsDefaultClick} | onClick={this.handleSetAsDefaultClick} | ||||
> | > |
import AlertWarnIcon from '../../../components/icons/AlertWarnIcon'; | import AlertWarnIcon from '../../../components/icons/AlertWarnIcon'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { getQualityGateUrl } from '../../../helpers/urls'; | import { getQualityGateUrl } from '../../../helpers/urls'; | ||||
import { QualityGate } from '../../../types/types'; | |||||
import { CaycStatus, QualityGate } from '../../../types/types'; | |||||
import BuiltInQualityGateBadge from './BuiltInQualityGateBadge'; | import BuiltInQualityGateBadge from './BuiltInQualityGateBadge'; | ||||
interface Props { | interface Props { | ||||
)} | )} | ||||
{qualityGate.isBuiltIn && <BuiltInQualityGateBadge className="little-spacer-left" />} | {qualityGate.isBuiltIn && <BuiltInQualityGateBadge className="little-spacer-left" />} | ||||
{!qualityGate.isCaycCompliant && ( | |||||
{qualityGate.caycStatus === CaycStatus.NonCompliant && ( | |||||
<> | <> | ||||
{/* Adding a11y-hidden span for accessibility */} | {/* Adding a11y-hidden span for accessibility */} | ||||
<span className="a11y-hidden">{translate('quality_gates.cayc.tooltip.message')}</span> | <span className="a11y-hidden">{translate('quality_gates.cayc.tooltip.message')}</span> |
expect(conditionsWrapper.getAllByText('Coverage')).toHaveLength(2); // This quality gate has duplicate condition | expect(conditionsWrapper.getAllByText('Coverage')).toHaveLength(2); // This quality gate has duplicate condition | ||||
expect(conditionsWrapper.getByText('Duplicated Lines (%)')).toBeInTheDocument(); | 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 () => { | it('should show success banner when quality gate is CAYC compliant', async () => { | ||||
expect(await conditionsWrapper.findByText('Duplicated Lines (%)')).toBeInTheDocument(); | 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 () => { | it('should unlock editing option for CAYC conditions', async () => { | ||||
const user = userEvent.setup(); | const user = userEvent.setup(); | ||||
handler.setIsAdmin(true); | handler.setIsAdmin(true); |
color: var(--alertTextSuccess); | color: var(--alertTextSuccess); | ||||
} | } | ||||
.cayc-over-compliant-header { | |||||
color: var(--veryDarkBlue); | |||||
} | |||||
.cayc-warning-description { | .cayc-warning-description { | ||||
line-height: 18px; | line-height: 18px; | ||||
} | } | ||||
.red-text { | |||||
color: var(--red); | |||||
} | |||||
.green-text { | |||||
color: var(--success500); | |||||
} | |||||
.strike-through { | |||||
text-decoration: line-through; | |||||
} |
*/ | */ | ||||
import { getLocalizedMetricName } from '../../helpers/l10n'; | import { getLocalizedMetricName } from '../../helpers/l10n'; | ||||
import { isDiffMetric } from '../../helpers/measures'; | 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 } = { | const CAYC_CONDITIONS: { [key: string]: Condition } = { | ||||
new_reliability_rating: { | new_reliability_rating: { | ||||
}; | }; | ||||
export const CAYC_CONDITIONS_WITHOUT_FIXED_VALUE = ['new_duplicated_lines_density', 'new_coverage']; | 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) { | export function isCaycCondition(condition: Condition) { | ||||
return condition.metric in CAYC_CONDITIONS; | return condition.metric in CAYC_CONDITIONS; | ||||
const result: { | const result: { | ||||
weakConditions: Condition[]; | weakConditions: Condition[]; | ||||
missingConditions: Condition[]; | missingConditions: Condition[]; | ||||
nonCaycConditions: Condition[]; | |||||
} = { | } = { | ||||
weakConditions: [], | weakConditions: [], | ||||
missingConditions: [], | missingConditions: [], | ||||
nonCaycConditions: [], | |||||
}; | }; | ||||
Object.keys(CAYC_CONDITIONS).forEach((key) => { | Object.keys(CAYC_CONDITIONS).forEach((key) => { | ||||
const selectedCondition = conditions.find((condition) => condition.metric === key); | const selectedCondition = conditions.find((condition) => condition.metric === key); | ||||
result.weakConditions.push(selectedCondition); | result.weakConditions.push(selectedCondition); | ||||
} | } | ||||
}); | }); | ||||
result.nonCaycConditions = getNonCaycConditions(conditions); | |||||
return result; | return result; | ||||
} | } | ||||
}); | }); | ||||
} | } | ||||
export function getNonCaycConditions(conditions: Condition[]) { | |||||
return conditions.filter((condition) => !isCaycCondition(condition)); | |||||
} | |||||
export function getCorrectCaycCondition(condition: Condition) { | export function getCorrectCaycCondition(condition: Condition) { | ||||
if (CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(condition.metric)) { | if (CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(condition.metric)) { | ||||
return condition; | return condition; | ||||
const oldConditions = qualityGate.conditions || []; | const oldConditions = qualityGate.conditions || []; | ||||
const conditions = [...oldConditions, condition]; | const conditions = [...oldConditions, condition]; | ||||
if (conditions) { | if (conditions) { | ||||
qualityGate.isCaycCompliant = updateCaycComplaintStatus(conditions); | |||||
qualityGate.caycStatus = updateCaycCompliantStatus(conditions); | |||||
} | } | ||||
return { ...qualityGate, conditions }; | return { ...qualityGate, conditions }; | ||||
} | } | ||||
const conditions = | const conditions = | ||||
qualityGate.conditions && qualityGate.conditions.filter((candidate) => candidate !== condition); | qualityGate.conditions && qualityGate.conditions.filter((candidate) => candidate !== condition); | ||||
if (conditions) { | if (conditions) { | ||||
qualityGate.isCaycCompliant = updateCaycComplaintStatus(conditions); | |||||
qualityGate.caycStatus = updateCaycCompliantStatus(conditions); | |||||
} | } | ||||
return { ...qualityGate, conditions }; | return { ...qualityGate, conditions }; | ||||
} | } | ||||
return candidate === oldCondition ? newCondition : candidate; | return candidate === oldCondition ? newCondition : candidate; | ||||
}); | }); | ||||
if (conditions) { | if (conditions) { | ||||
qualityGate.isCaycCompliant = updateCaycComplaintStatus(conditions); | |||||
qualityGate.caycStatus = updateCaycCompliantStatus(conditions); | |||||
} | } | ||||
return { ...qualityGate, 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)) { | for (const key of Object.keys(CAYC_CONDITIONS)) { | ||||
const selectedCondition = conditions.find((condition) => condition.metric === key); | const selectedCondition = conditions.find((condition) => condition.metric === key); | ||||
if (!selectedCondition) { | if (!selectedCondition) { | ||||
return false; | |||||
return CaycStatus.NonCompliant; | |||||
} | } | ||||
if ( | if ( | ||||
selectedCondition && | selectedCondition && | ||||
selectedCondition.error !== CAYC_CONDITIONS[key].error | 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) { | export function getPossibleOperators(metric: Metric) { |
QualityGateStatusCondition, | QualityGateStatusCondition, | ||||
QualityGateStatusConditionEnhanced, | QualityGateStatusConditionEnhanced, | ||||
} from '../../types/quality-gates'; | } from '../../types/quality-gates'; | ||||
import { QualityGate } from '../../types/types'; | |||||
import { CaycStatus, QualityGate } from '../../types/types'; | |||||
import { mockMeasureEnhanced, mockMetric } from '../testMocks'; | import { mockMeasureEnhanced, mockMetric } from '../testMocks'; | ||||
export function mockQualityGate(overrides: Partial<QualityGate> = {}): QualityGate { | export function mockQualityGate(overrides: Partial<QualityGate> = {}): QualityGate { | ||||
): QualityGateStatus { | ): QualityGateStatus { | ||||
return { | return { | ||||
ignoredConditions: false, | ignoredConditions: false, | ||||
isCaycCompliant: true, | |||||
caycStatus: CaycStatus.Compliant, | |||||
failedConditions: [mockQualityGateStatusConditionEnhanced()], | failedConditions: [mockQualityGateStatusConditionEnhanced()], | ||||
key: 'foo', | key: 'foo', | ||||
name: 'Foo', | name: 'Foo', | ||||
}, | }, | ||||
], | ], | ||||
ignoredConditions: false, | ignoredConditions: false, | ||||
isCaycCompliant: true, | |||||
caycStatus: CaycStatus.Compliant, | |||||
status: 'OK', | status: 'OK', | ||||
...overrides, | ...overrides, | ||||
}; | }; | ||||
value: '5', | value: '5', | ||||
}, | }, | ||||
], | ], | ||||
isCaycCompliant: true, | |||||
caycStatus: CaycStatus.Compliant, | |||||
status: 'ERROR', | status: 'ERROR', | ||||
}, | }, | ||||
{ | { | ||||
value: '15', | value: '15', | ||||
}, | }, | ||||
], | ], | ||||
isCaycCompliant: true, | |||||
caycStatus: CaycStatus.Compliant, | |||||
status: 'ERROR', | status: 'ERROR', | ||||
}, | }, | ||||
], | ], |
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import { BranchLike } from './branch-like'; | import { BranchLike } from './branch-like'; | ||||
import { MeasureEnhanced, Metric, Status } from './types'; | |||||
import { CaycStatus, MeasureEnhanced, Metric, Status } from './types'; | |||||
import { UserBase } from './users'; | import { UserBase } from './users'; | ||||
export interface QualityGateProjectStatus { | export interface QualityGateProjectStatus { | ||||
conditions?: QualityGateProjectStatusCondition[]; | conditions?: QualityGateProjectStatusCondition[]; | ||||
ignoredConditions: boolean; | ignoredConditions: boolean; | ||||
status: Status; | status: Status; | ||||
isCaycCompliant: boolean; | |||||
caycStatus: CaycStatus; | |||||
} | } | ||||
export interface QualityGateProjectStatusCondition { | export interface QualityGateProjectStatusCondition { | ||||
key: string; | key: string; | ||||
name: string; | name: string; | ||||
status: Status; | status: Status; | ||||
isCaycCompliant: boolean; | |||||
caycStatus: CaycStatus; | |||||
} | } | ||||
export interface QualityGateStatus { | export interface QualityGateStatus { | ||||
failedConditions: QualityGateStatusConditionEnhanced[]; | failedConditions: QualityGateStatusConditionEnhanced[]; | ||||
ignoredConditions?: boolean; | ignoredConditions?: boolean; | ||||
isCaycCompliant: boolean; | |||||
caycStatus: CaycStatus; | |||||
key: string; | key: string; | ||||
name: string; | name: string; | ||||
status: Status; | status: Status; |
url: string; | url: string; | ||||
} | } | ||||
export enum CaycStatus { | |||||
Compliant = 'compliant', | |||||
NonCompliant = 'non-compliant', | |||||
OverCompliant = 'over-compliant', | |||||
} | |||||
export interface QualityGate { | export interface QualityGate { | ||||
actions?: { | actions?: { | ||||
associateProjects?: boolean; | associateProjects?: boolean; | ||||
}; | }; | ||||
conditions?: Condition[]; | conditions?: Condition[]; | ||||
isBuiltIn?: boolean; | isBuiltIn?: boolean; | ||||
isCaycCompliant?: boolean; | |||||
caycStatus?: CaycStatus; | |||||
isDefault?: boolean; | isDefault?: boolean; | ||||
name: string; | name: string; | ||||
} | } |
for (QualityGateDto qualityGateDto : qualityGateDtos) { | for (QualityGateDto qualityGateDto : qualityGateDtos) { | ||||
qualityGates.add( | qualityGates.add( | ||||
new TelemetryData.QualityGate(qualityGateDto.getUuid(), qualityGateCaycChecker.checkCaycCompliant(dbSession, | new TelemetryData.QualityGate(qualityGateDto.getUuid(), qualityGateCaycChecker.checkCaycCompliant(dbSession, | ||||
qualityGateDto.getUuid())) | |||||
qualityGateDto.getUuid()).toString()) | |||||
); | ); | ||||
} | } | ||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.assertj.core.api.Assertions.entry; | import static org.assertj.core.api.Assertions.entry; | ||||
import static org.assertj.core.groups.Tuple.tuple; | 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.mock; | ||||
import static org.mockito.Mockito.spy; | import static org.mockito.Mockito.spy; | ||||
import static org.mockito.Mockito.when; | import static org.mockito.Mockito.when; | ||||
import static org.sonar.db.component.BranchType.BRANCH; | 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_CPP_KEY; | ||||
import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_C_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; | import static org.sonar.server.telemetry.TelemetryDataLoaderImpl.SCIM_PROPERTY_ENABLED; | ||||
@RunWith(DataProviderRunner.class) | @RunWith(DataProviderRunner.class) | ||||
public void setUpBuiltInQualityGate() { | public void setUpBuiltInQualityGate() { | ||||
String builtInQgName = "Sonar Way"; | String builtInQgName = "Sonar Way"; | ||||
builtInDefaultQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName(builtInQgName).setBuiltIn(true)); | builtInDefaultQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName(builtInQgName).setBuiltIn(true)); | ||||
when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(NON_COMPLIANT); | |||||
db.qualityGates().setDefaultQualityGate(builtInDefaultQualityGate); | db.qualityGates().setDefaultQualityGate(builtInDefaultQualityGate); | ||||
bugsDto = db.measures().insertMetric(m -> m.setKey(BUGS_KEY)); | bugsDto = db.measures().insertMetric(m -> m.setKey(BUGS_KEY)); | ||||
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, 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())); | tuple(1L, 0L, builtInDefaultQualityGate.getUuid(), "scm-2", "ci-2", "github_cloud", Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())); | ||||
assertThat(data.getQualityGates()) | assertThat(data.getQualityGates()) | ||||
.extracting(TelemetryData.QualityGate::uuid, TelemetryData.QualityGate::isCaycCompliant) | |||||
.extracting(TelemetryData.QualityGate::uuid, TelemetryData.QualityGate::caycStatus) | |||||
.containsExactlyInAnyOrder( | .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") | |||||
); | ); | ||||
} | } | ||||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED; | 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.api.measures.CoreMetrics.NEW_SECURITY_RATING; | ||||
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; | 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 { | public class QualityGateCaycChecker { | ||||
this.dbClient = dbClient; | this.dbClient = dbClient; | ||||
} | } | ||||
public boolean checkCaycCompliant(DbSession dbSession, String qualityGateUuid) { | |||||
public QualityGateCaycStatus checkCaycCompliant(DbSession dbSession, String qualityGateUuid) { | |||||
var conditionsByMetricId = dbClient.gateConditionDao().selectForQualityGate(dbSession, qualityGateUuid) | var conditionsByMetricId = dbClient.gateConditionDao().selectForQualityGate(dbSession, qualityGateUuid) | ||||
.stream() | .stream() | ||||
.collect(uniqueIndex(QualityGateConditionDto::getMetricUuid)); | .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()) | var metrics = dbClient.metricDao().selectByUuids(dbSession, conditionsByMetricId.keySet()) | ||||
.filter(metric -> checkMetricCaycCompliant(conditionsByMetricId.get(metric.getUuid()), metric)) | .filter(metric -> checkMetricCaycCompliant(conditionsByMetricId.get(metric.getUuid()), metric)) | ||||
.count(); | .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)) | return Optional.ofNullable(dbClient.qualityGateDao().selectByProjectUuid(dbSession, projectUuid)) | ||||
.or(() -> Optional.ofNullable(dbClient.qualityGateDao().selectDefault(dbSession))) | .or(() -> Optional.ofNullable(dbClient.qualityGateDao().selectDefault(dbSession))) | ||||
.map(qualityGate -> checkCaycCompliant(dbSession, qualityGate.getUuid())) | .map(qualityGate -> checkCaycCompliant(dbSession, qualityGate.getUuid())) | ||||
.orElse(false); | |||||
.orElse(NON_COMPLIANT); | |||||
} | } | ||||
private static boolean checkMetricCaycCompliant(QualityGateConditionDto condition, MetricDto metric) { | private static boolean checkMetricCaycCompliant(QualityGateConditionDto condition, MetricDto metric) { |
/* | |||||
* 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; | |||||
} | |||||
} |
.setSince("4.3") | .setSince("4.3") | ||||
.setResponseExample(Resources.getResource(this.getClass(), "list-example.json")) | .setResponseExample(Resources.getResource(this.getClass(), "list-example.json")) | ||||
.setChangelog( | .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("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", "'isDefault' field is added on quality gate"), | ||||
new Change("7.0", "'default' field on root level is deprecated"), | new Change("7.0", "'default' field on root level is deprecated"), | ||||
.setName(qualityGate.getName()) | .setName(qualityGate.getName()) | ||||
.setIsDefault(qualityGate.getUuid().equals(defaultUuid)) | .setIsDefault(qualityGate.getUuid().equals(defaultUuid)) | ||||
.setIsBuiltIn(qualityGate.isBuiltIn()) | .setIsBuiltIn(qualityGate.isBuiltIn()) | ||||
.setIsCaycCompliant(qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid())) | |||||
.setCaycStatus(qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid()).toString()) | |||||
.setActions(wsSupport.getActions(dbSession, qualityGate, defaultQualityGate)) | .setActions(wsSupport.getActions(dbSession, qualityGate, defaultQualityGate)) | ||||
.build()) | .build()) | ||||
.collect(toList())); | .collect(toList())); |
import org.sonar.server.component.ComponentFinder; | import org.sonar.server.component.ComponentFinder; | ||||
import org.sonar.server.exceptions.BadRequestException; | import org.sonar.server.exceptions.BadRequestException; | ||||
import org.sonar.server.qualitygate.QualityGateCaycChecker; | import org.sonar.server.qualitygate.QualityGateCaycChecker; | ||||
import org.sonar.server.qualitygate.QualityGateCaycStatus; | |||||
import org.sonar.server.user.UserSession; | import org.sonar.server.user.UserSession; | ||||
import org.sonar.server.ws.KeyExamples; | import org.sonar.server.ws.KeyExamples; | ||||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse; | import org.sonarqube.ws.Qualitygates.ProjectStatusResponse; | ||||
.setSince("5.3") | .setSince("5.3") | ||||
.setHandler(this) | .setHandler(this) | ||||
.setChangelog( | .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("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("8.5", "The field 'periods' in the response is deprecated. Use 'period' instead"), | ||||
new Change("7.7", "The parameters 'branch' and 'pullRequest' were added"), | new Change("7.7", "The parameters 'branch' and 'pullRequest' were added"), | ||||
ProjectAndSnapshot projectAndSnapshot = getProjectAndSnapshot(dbSession, analysisId, projectUuid, projectKey, branchKey, pullRequestId); | ProjectAndSnapshot projectAndSnapshot = getProjectAndSnapshot(dbSession, analysisId, projectUuid, projectKey, branchKey, pullRequestId); | ||||
checkPermission(projectAndSnapshot.project); | checkPermission(projectAndSnapshot.project); | ||||
Optional<String> measureData = loadQualityGateDetails(dbSession, projectAndSnapshot, analysisId != null); | 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() | 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(); | .build(); | ||||
} | } | ||||
import java.util.stream.StreamSupport; | import java.util.stream.StreamSupport; | ||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.sonar.db.component.SnapshotDto; | import org.sonar.db.component.SnapshotDto; | ||||
import org.sonar.server.qualitygate.QualityGateCaycStatus; | |||||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse; | import org.sonarqube.ws.Qualitygates.ProjectStatusResponse; | ||||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.NewCodePeriod; | import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.NewCodePeriod; | ||||
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Period; | import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Period; | ||||
public class QualityGateDetailsFormatter { | public class QualityGateDetailsFormatter { | ||||
private final Optional<String> optionalMeasureData; | private final Optional<String> optionalMeasureData; | ||||
private final Optional<SnapshotDto> optionalSnapshot; | private final Optional<SnapshotDto> optionalSnapshot; | ||||
private final boolean isCaycCompliant; | |||||
private final QualityGateCaycStatus caycStatus; | |||||
private final ProjectStatusResponse.ProjectStatus.Builder projectStatusBuilder; | 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.optionalMeasureData = Optional.ofNullable(measureData); | ||||
this.optionalSnapshot = Optional.ofNullable(snapshot); | this.optionalSnapshot = Optional.ofNullable(snapshot); | ||||
this.isCaycCompliant = isCaycCompliant; | |||||
this.caycStatus = caycStatus; | |||||
this.projectStatusBuilder = ProjectStatusResponse.ProjectStatus.newBuilder(); | this.projectStatusBuilder = ProjectStatusResponse.ProjectStatus.newBuilder(); | ||||
} | } | ||||
ProjectStatusResponse.Status qualityGateStatus = measureLevelToQualityGateStatus(json.get("level").getAsString()); | ProjectStatusResponse.Status qualityGateStatus = measureLevelToQualityGateStatus(json.get("level").getAsString()); | ||||
projectStatusBuilder.setStatus(qualityGateStatus); | projectStatusBuilder.setStatus(qualityGateStatus); | ||||
projectStatusBuilder.setIsCaycCompliant(isCaycCompliant); | |||||
projectStatusBuilder.setCaycStatus(caycStatus.toString()); | |||||
formatIgnoredConditions(json); | formatIgnoredConditions(json); | ||||
formatConditions(json.getAsJsonArray("conditions")); | formatConditions(json.getAsJsonArray("conditions")); | ||||
} | } | ||||
private ProjectStatusResponse.ProjectStatus newResponseWithoutQualityGateDetails() { | 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() { | private static Predicate<JsonObject> isConditionOnValidPeriod() { |
import org.sonar.db.qualitygate.QualityGateConditionDto; | import org.sonar.db.qualitygate.QualityGateConditionDto; | ||||
import org.sonar.db.qualitygate.QualityGateDto; | import org.sonar.db.qualitygate.QualityGateDto; | ||||
import org.sonar.server.qualitygate.QualityGateCaycChecker; | import org.sonar.server.qualitygate.QualityGateCaycChecker; | ||||
import org.sonar.server.qualitygate.QualityGateCaycStatus; | |||||
import org.sonar.server.qualitygate.QualityGateFinder; | import org.sonar.server.qualitygate.QualityGateFinder; | ||||
import org.sonarqube.ws.Qualitygates.ShowWsResponse; | import org.sonarqube.ws.Qualitygates.ShowWsResponse; | ||||
.setSince("4.3") | .setSince("4.3") | ||||
.setResponseExample(Resources.getResource(this.getClass(), "show-example.json")) | .setResponseExample(Resources.getResource(this.getClass(), "show-example.json")) | ||||
.setChangelog( | .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", "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("8.4", "Field 'id' in the response is deprecated."), | ||||
new Change("7.6", "'period' and 'warning' fields of conditions are removed from the response"), | new Change("7.6", "'period' and 'warning' fields of conditions are removed from the response"), | ||||
Collection<QualityGateConditionDto> conditions = getConditions(dbSession, qualityGate); | Collection<QualityGateConditionDto> conditions = getConditions(dbSession, qualityGate); | ||||
Map<String, MetricDto> metricsByUuid = getMetricsByUuid(dbSession, conditions); | Map<String, MetricDto> metricsByUuid = getMetricsByUuid(dbSession, conditions); | ||||
QualityGateDto defaultQualityGate = qualityGateFinder.getDefault(dbSession); | 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); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
private ShowWsResponse buildResponse(DbSession dbSession, QualityGateDto qualityGate, QualityGateDto defaultQualityGate, Collection<QualityGateConditionDto> conditions, | 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() | return ShowWsResponse.newBuilder() | ||||
.setId(qualityGate.getUuid()) | .setId(qualityGate.getUuid()) | ||||
.setName(qualityGate.getName()) | .setName(qualityGate.getName()) | ||||
.setIsBuiltIn(qualityGate.isBuiltIn()) | .setIsBuiltIn(qualityGate.isBuiltIn()) | ||||
.setIsCaycCompliant(isCaycCompliant) | |||||
.setCaycStatus(caycStatus.toString()) | |||||
.addAllConditions(conditions.stream() | .addAllConditions(conditions.stream() | ||||
.map(toWsCondition(metricsByUuid)) | .map(toWsCondition(metricsByUuid)) | ||||
.collect(toList())) | .collect(toList())) |
"projectStatus": { | "projectStatus": { | ||||
"status": "ERROR", | "status": "ERROR", | ||||
"ignoredConditions": false, | "ignoredConditions": false, | ||||
"isCaycCompliant": false, | |||||
"caycStatus": "non-compliant", | |||||
"conditions": [ | "conditions": [ | ||||
{ | { | ||||
"status": "ERROR", | "status": "ERROR", |
import org.sonar.db.metric.MetricDto; | import org.sonar.db.metric.MetricDto; | ||||
import org.sonar.db.qualitygate.QualityGateConditionDto; | 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.DUPLICATED_LINES; | ||||
import static org.sonar.api.measures.CoreMetrics.LINE_COVERAGE; | import static org.sonar.api.measures.CoreMetrics.LINE_COVERAGE; | ||||
import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE; | import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE; | ||||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED; | 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.api.measures.CoreMetrics.NEW_SECURITY_RATING; | ||||
import static org.sonar.server.qualitygate.QualityGateCaycChecker.CAYC_METRICS; | 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 { | public class QualityGateCaycCheckerTest { | ||||
public void checkCaycCompliant() { | public void checkCaycCompliant() { | ||||
String qualityGateUuid = "abcd"; | String qualityGateUuid = "abcd"; | ||||
CAYC_METRICS.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | 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 | @Test | ||||
// extra conditions outside of CAYC requirements | // extra conditions outside of CAYC requirements | ||||
List.of(LINE_COVERAGE, DUPLICATED_LINES).forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | 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 | @Test | ||||
var metric = metrics.get(i); | var metric = metrics.get(i); | ||||
insertCondition(metric, qualityGateUuid, idx == i ? metric.getWorstValue() : metric.getBestValue()); | insertCondition(metric, qualityGateUuid, idx == i ? metric.getWorstValue() : metric.getBestValue()); | ||||
} | } | ||||
assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isFalse(); | |||||
assertEquals(NON_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)); | |||||
}); | }); | ||||
} | } | ||||
String qualityGateUuid = "abcd"; | String qualityGateUuid = "abcd"; | ||||
List.of(NEW_MAINTAINABILITY_RATING, NEW_RELIABILITY_RATING, NEW_SECURITY_HOTSPOTS_REVIEWED, NEW_DUPLICATED_LINES_DENSITY) | List.of(NEW_MAINTAINABILITY_RATING, NEW_RELIABILITY_RATING, NEW_SECURITY_HOTSPOTS_REVIEWED, NEW_DUPLICATED_LINES_DENSITY) | ||||
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | .forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | ||||
assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isFalse(); | |||||
assertEquals(NON_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)); | |||||
} | } | ||||
@Test | @Test | ||||
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | .forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | ||||
List.of(NEW_COVERAGE, NEW_DUPLICATED_LINES_DENSITY) | List.of(NEW_COVERAGE, NEW_DUPLICATED_LINES_DENSITY) | ||||
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getWorstValue())); | .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) { | private void insertCondition(MetricDto metricDto, String qualityGateUuid, Double threshold) { |
*/ | */ | ||||
package org.sonar.server.qualitygate.ws; | package org.sonar.server.qualitygate.ws; | ||||
import org.junit.Before; | |||||
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.sonar.api.server.ws.WebService; | import org.sonar.api.server.ws.WebService; | ||||
import static org.mockito.Mockito.when; | 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_GATES; | ||||
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES; | 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.sonar.test.JsonAssert.assertJson; | ||||
import static org.sonarqube.ws.Qualitygates.ListWsResponse; | import static org.sonarqube.ws.Qualitygates.ListWsResponse; | ||||
private final WsActionTester ws = new WsActionTester(new ListAction(db.getDbClient(), | private final WsActionTester ws = new WsActionTester(new ListAction(db.getDbClient(), | ||||
new QualityGatesWsSupport(dbClient, userSession, TestComponentFinder.from(db)), qualityGateFinder, qualityGateCaycChecker)); | new QualityGatesWsSupport(dbClient, userSession, TestComponentFinder.from(db)), qualityGateFinder, qualityGateCaycChecker)); | ||||
@Before | |||||
public void setUp() { | |||||
when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(COMPLIANT); | |||||
} | |||||
@Test | @Test | ||||
public void list_quality_gates() { | public void list_quality_gates() { | ||||
QualityGateDto defaultQualityGate = db.qualityGates().insertQualityGate(); | QualityGateDto defaultQualityGate = db.qualityGates().insertQualityGate(); | ||||
} | } | ||||
@Test | @Test | ||||
public void test_isCaycCompliant_flag() { | |||||
public void test_caycStatus_flag() { | |||||
QualityGateDto qualityGate1 = db.qualityGates().insertQualityGate(); | QualityGateDto qualityGate1 = db.qualityGates().insertQualityGate(); | ||||
QualityGateDto qualityGate2 = 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); | db.qualityGates().setDefaultQualityGate(qualityGate1); | ||||
.executeProtobuf(ListWsResponse.class); | .executeProtobuf(ListWsResponse.class); | ||||
assertThat(response.getQualitygatesList()) | assertThat(response.getQualitygatesList()) | ||||
.extracting(QualityGate::getId, QualityGate::getIsCaycCompliant) | |||||
.extracting(QualityGate::getId, QualityGate::getCaycStatus) | |||||
.containsExactlyInAnyOrder( | .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 | @Test |
import java.nio.charset.StandardCharsets; | import java.nio.charset.StandardCharsets; | ||||
import org.apache.commons.io.IOUtils; | import org.apache.commons.io.IOUtils; | ||||
import org.apache.commons.lang.RandomStringUtils; | import org.apache.commons.lang.RandomStringUtils; | ||||
import org.junit.Before; | |||||
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.sonar.api.measures.CoreMetrics; | import org.sonar.api.measures.CoreMetrics; | ||||
import org.sonar.db.component.SnapshotDto; | import org.sonar.db.component.SnapshotDto; | ||||
import org.sonar.db.metric.MetricDto; | import org.sonar.db.metric.MetricDto; | ||||
import org.sonar.db.permission.GlobalPermission; | import org.sonar.db.permission.GlobalPermission; | ||||
import org.sonar.db.qualitygate.QualityGateDto; | |||||
import org.sonar.server.component.TestComponentFinder; | import org.sonar.server.component.TestComponentFinder; | ||||
import org.sonar.server.exceptions.BadRequestException; | import org.sonar.server.exceptions.BadRequestException; | ||||
import org.sonar.server.exceptions.ForbiddenException; | import org.sonar.server.exceptions.ForbiddenException; | ||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||||
import static org.assertj.core.api.Assertions.tuple; | 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.any; | ||||
import static org.mockito.ArgumentMatchers.eq; | import static org.mockito.ArgumentMatchers.eq; | ||||
import static org.mockito.Mockito.mock; | import static org.mockito.Mockito.mock; | ||||
import static org.sonar.db.measure.MeasureTesting.newLiveMeasure; | import static org.sonar.db.measure.MeasureTesting.newLiveMeasure; | ||||
import static org.sonar.db.measure.MeasureTesting.newMeasureDto; | import static org.sonar.db.measure.MeasureTesting.newMeasureDto; | ||||
import static org.sonar.db.metric.MetricTesting.newMetricDto; | 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_ANALYSIS_ID; | ||||
import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_BRANCH; | import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_BRANCH; | ||||
import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_PROJECT_ID; | import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_PROJECT_ID; | ||||
private final WsActionTester ws = new WsActionTester(new ProjectStatusAction(dbClient, TestComponentFinder.from(db), userSession, qualityGateCaycChecker)); | 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 | @Test | ||||
public void test_definition() { | public void test_definition() { | ||||
WebService.Action action = ws.getDef(); | WebService.Action action = ws.getDef(); | ||||
ComponentDto project = db.components().insertPrivateProject(); | ComponentDto project = db.components().insertPrivateProject(); | ||||
var qg = db.qualityGates().insertBuiltInQualityGate(); | var qg = db.qualityGates().insertBuiltInQualityGate(); | ||||
db.qualityGates().setDefaultQualityGate(qg); | 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)); | SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(project)); | ||||
dbSession.commit(); | dbSession.commit(); | ||||
userSession.addProjectPermission(UserRole.USER, project); | userSession.addProjectPermission(UserRole.USER, project); | ||||
.setParam(PARAM_ANALYSIS_ID, snapshot.getUuid()) | .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid()) | ||||
.executeProtobuf(ProjectStatusResponse.class); | .executeProtobuf(ProjectStatusResponse.class); | ||||
assertThat(result.getProjectStatus().getIsCaycCompliant()).isTrue(); | |||||
assertEquals(COMPLIANT.toString(), result.getProjectStatus().getCaycStatus()); | |||||
} | } | ||||
@Test | @Test |
import java.io.IOException; | import java.io.IOException; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Optional; | |||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.apache.commons.io.IOUtils; | import org.apache.commons.io.IOUtils; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | 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.api.utils.DateUtils.formatDateTime; | ||||
import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT; | |||||
public class QualityGateDetailsFormatterTest { | public class QualityGateDetailsFormatterTest { | ||||
ProjectStatus result = underTest.format(); | ProjectStatus result = underTest.format(); | ||||
assertThat(result.getStatus()).isEqualTo(ProjectStatusResponse.Status.ERROR); | assertThat(result.getStatus()).isEqualTo(ProjectStatusResponse.Status.ERROR); | ||||
assertThat(result.getIsCaycCompliant()).isFalse(); | |||||
assertEquals(NON_COMPLIANT.toString(), result.getCaycStatus()); | |||||
// check conditions | // check conditions | ||||
assertThat(result.getConditionsCount()).isEqualTo(3); | assertThat(result.getConditionsCount()).isEqualTo(3); | ||||
List<ProjectStatusResponse.Condition> conditions = result.getConditionsList(); | List<ProjectStatusResponse.Condition> conditions = result.getConditionsList(); | ||||
} | } | ||||
private static QualityGateDetailsFormatter newQualityGateDetailsFormatter(@Nullable String measureData, @Nullable SnapshotDto snapshotDto) { | private static QualityGateDetailsFormatter newQualityGateDetailsFormatter(@Nullable String measureData, @Nullable SnapshotDto snapshotDto) { | ||||
return new QualityGateDetailsFormatter(measureData, snapshotDto, false); | |||||
return new QualityGateDetailsFormatter(measureData, snapshotDto, NON_COMPLIANT); | |||||
} | } | ||||
} | } |
*/ | */ | ||||
package org.sonar.server.qualitygate.ws; | package org.sonar.server.qualitygate.ws; | ||||
import org.junit.Before; | |||||
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.sonar.api.server.ws.WebService; | import org.sonar.api.server.ws.WebService; | ||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||||
import static org.assertj.core.api.AssertionsForClassTypes.tuple; | 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.any; | ||||
import static org.mockito.ArgumentMatchers.eq; | import static org.mockito.ArgumentMatchers.eq; | ||||
import static org.mockito.Mockito.mock; | import static org.mockito.Mockito.mock; | ||||
import static org.mockito.Mockito.when; | 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_GATES; | ||||
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES; | 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.sonar.test.JsonAssert.assertJson; | ||||
import static org.sonarqube.ws.Qualitygates.Actions; | import static org.sonarqube.ws.Qualitygates.Actions; | ||||
new ShowAction(db.getDbClient(), new QualityGateFinder(db.getDbClient()), | new ShowAction(db.getDbClient(), new QualityGateFinder(db.getDbClient()), | ||||
new QualityGatesWsSupport(db.getDbClient(), userSession, TestComponentFinder.from(db)), qualityGateCaycChecker)); | new QualityGatesWsSupport(db.getDbClient(), userSession, TestComponentFinder.from(db)), qualityGateCaycChecker)); | ||||
@Before | |||||
public void setUp() { | |||||
when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(COMPLIANT); | |||||
} | |||||
@Test | @Test | ||||
public void show() { | public void show() { | ||||
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); | QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); | ||||
@Test | @Test | ||||
public void show_isCaycCompliant() { | public void show_isCaycCompliant() { | ||||
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); | 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); | db.qualityGates().setDefaultQualityGate(qualityGate); | ||||
ShowWsResponse response = ws.newRequest() | ShowWsResponse response = ws.newRequest() | ||||
.setParam("name", qualityGate.getName()) | .setParam("name", qualityGate.getName()) | ||||
.executeProtobuf(ShowWsResponse.class); | .executeProtobuf(ShowWsResponse.class); | ||||
assertThat(response.getIsCaycCompliant()).isTrue(); | |||||
assertEquals(COMPLIANT.toString(), response.getCaycStatus()); | |||||
} | } | ||||
@Test | @Test |
quality_gates.cayc_condition.review_update=Review and Fix Quality Gate | 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.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.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.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_maintainability_rating=Technical debt ratio is greater than {1} | ||||
quality_gates.cayc.new_reliability_rating.A=No bugs | quality_gates.cayc.new_reliability_rating.A=No bugs | ||||
quality_gates.cayc.unlock_edit=Unlock editing | 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.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.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.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.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 | quality_gates.cayc.banner.list_item1=No bugs | ||||
quality_gates.cayc.banner.list_item3=No security hotspots to review | 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_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.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 | |||||
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ | ||||
# | # | ||||
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_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.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.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.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.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.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_gate.hide_project_conditions_x=Hide failed conditions for project {0} | ||||
overview.quality_profiles=Quality Profiles used | overview.quality_profiles=Quality Profiles used |
repeated Period periods = 3; | repeated Period periods = 3; | ||||
optional bool ignoredConditions = 4; | optional bool ignoredConditions = 4; | ||||
optional NewCodePeriod period = 5; | optional NewCodePeriod period = 5; | ||||
optional bool isCaycCompliant = 6; | |||||
optional string caycStatus = 6; | |||||
} | } | ||||
message Condition { | message Condition { | ||||
repeated Condition conditions = 3; | repeated Condition conditions = 3; | ||||
optional bool isBuiltIn = 4; | optional bool isBuiltIn = 4; | ||||
optional Actions actions = 5; | optional Actions actions = 5; | ||||
optional bool isCaycCompliant = 6; | |||||
optional string caycStatus = 6; | |||||
message Condition { | message Condition { | ||||
optional string id = 1; | optional string id = 1; | ||||
optional bool isDefault = 3; | optional bool isDefault = 3; | ||||
optional bool isBuiltIn = 4; | optional bool isBuiltIn = 4; | ||||
optional Actions actions = 5; | optional Actions actions = 5; | ||||
optional bool isCaycCompliant = 6; | |||||
optional string caycStatus = 6; | |||||
} | } | ||||
message RootActions { | message RootActions { |