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 {
statistics.getQualityGates().forEach(qualityGate -> {
json.beginObject();
json.prop("uuid", qualityGate.uuid());
- json.prop("isCaycCompliant", qualityGate.isCaycCompliant());
+ json.prop("caycStatus", qualityGate.caycStatus());
json.endObject();
});
json.endArray();
"quality-gates": [
{
"uuid": "uuid-0",
- "isCaycCompliant": true
+ "caycStatus": "non-compliant"
},
{
"uuid": "uuid-1",
- "isCaycCompliant": false
+ "caycStatus": "compliant"
},
{
"uuid": "uuid-2",
- "isCaycCompliant": true
+ "caycStatus": "over-compliant"
}
]
}
}
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
import { mockUserBase } from '../../helpers/mocks/users';
import { mockCondition, mockGroup } from '../../helpers/testMocks';
import { MetricKey } from '../../types/metrics';
-import { Condition, QualityGate } from '../../types/types';
+import { CaycStatus, Condition, QualityGate } from '../../types/types';
import {
addGroup,
addUser,
],
isDefault: true,
isBuiltIn: false,
- isCaycCompliant: true,
+ caycStatus: CaycStatus.Compliant,
}),
mockQualityGate({
name: 'SonarSource way - CFamily',
],
isDefault: false,
isBuiltIn: false,
+ caycStatus: CaycStatus.NonCompliant,
}),
mockQualityGate({
name: 'Sonar way',
],
isDefault: false,
isBuiltIn: true,
- isCaycCompliant: true,
+ caycStatus: CaycStatus.Compliant,
}),
mockQualityGate({
name: 'Non Cayc QG',
],
isDefault: false,
isBuiltIn: false,
- isCaycCompliant: false,
+ caycStatus: CaycStatus.NonCompliant,
+ }),
+ mockQualityGate({
+ name: 'Over Compliant CAYC QG',
+ conditions: [
+ { id: 'deprecatedoc', metric: 'function_complexity', op: 'LT', error: '1' },
+ { id: 'AXJMbIUHPAOIsUIE3eOFoc', metric: 'new_coverage', op: 'LT', error: '80' },
+ { id: 'AXJMbIUHPAOIsUIE3eNsoc', metric: 'new_security_rating', op: 'GT', error: '1' },
+ { id: 'AXJMbIUHPAOIsUIE3eODoc', metric: 'new_reliability_rating', op: 'GT', error: '1' },
+ {
+ id: 'AXJMbIUHPAOIsUIE3eOEoc',
+ metric: 'new_maintainability_rating',
+ op: 'GT',
+ error: '1',
+ },
+ { id: 'AXJMbIUHPAOIsUIE3eOFocdl', metric: 'new_coverage', op: 'LT', error: '80' },
+ {
+ id: 'AXJMbIUHPAOIsUIE3eOGoc',
+ metric: 'new_duplicated_lines_density',
+ op: 'GT',
+ error: '3',
+ },
+ {
+ id: 'AXJMbIUHPAOIsUIE3eOkoc',
+ metric: 'new_security_hotspots_reviewed',
+ op: 'LT',
+ error: '100',
+ },
+ ],
+ isDefault: false,
+ isBuiltIn: false,
+ caycStatus: CaycStatus.OverCompliant,
}),
mockQualityGate({
name: 'QG without conditions',
conditions: [],
isDefault: false,
isBuiltIn: false,
- isCaycCompliant: false,
+ caycStatus: CaycStatus.NonCompliant,
}),
mockQualityGate({
name: 'QG without new code conditions',
],
isDefault: false,
isBuiltIn: false,
- isCaycCompliant: false,
+ caycStatus: CaycStatus.NonCompliant,
}),
];
],
isDefault: false,
isBuiltIn: false,
- isCaycCompliant: true,
+ caycStatus: CaycStatus.Compliant,
})
);
return this.reply({
--- /dev/null
+/*
+ * 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) {
const qgStatuses = results
.map(({ measures = [], project, projectBranchLike }) => {
- const { key, name, status, isCaycCompliant } = project;
+ const { key, name, status, caycStatus } = project;
const conditions = extractStatusConditionsFromApplicationStatusChildProject(project);
const failedConditions = this.getFailedConditions(conditions, measures);
return {
failedConditions,
- isCaycCompliant,
+ caycStatus,
key,
name,
status,
this.loadMeasuresAndMeta(key, branch, metricKeys).then(
({ measures, metrics, period }) => {
if (this.mounted && measures) {
- const { ignoredConditions, isCaycCompliant, status } = projectStatus;
+ const { ignoredConditions, caycStatus, status } = projectStatus;
const conditions = extractStatusConditionsFromProjectStatus(projectStatus);
const failedConditions = this.getFailedConditions(conditions, measures);
const qgStatus = {
ignoredConditions,
- isCaycCompliant,
+ caycStatus,
failedConditions,
key,
name,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
import DocLink from '../../../components/common/DocLink';
import Link from '../../../components/common/Link';
import { Alert } from '../../../components/ui/Alert';
return (
<>
<Alert variant="warning">{translate('overview.quality_gate.conditions.cayc.warning')}</Alert>
-
- <p className="big-spacer-top big-spacer-bottom">
- {translate('overview.quality_gate.conditions.cayc.details')}
- </p>
-
- {component.qualityGate && (
- <div className="big-spacer-bottom">
- <Link className="button" to={getQualityGateUrl(component.qualityGate.name)}>
- {translate('overview.quality_gate.conditions.cayc.review')}
- </Link>
- </div>
+ {component.qualityGate ? (
+ <p className="big-spacer-top big-spacer-bottom">
+ <FormattedMessage
+ id="overview.quality_gate.conditions.cayc.details_with_link"
+ defaultMessage={translate('overview.quality_gate.conditions.cayc.details_with_link')}
+ values={{
+ link: (
+ <Link to={getQualityGateUrl(component.qualityGate.name)}>
+ {translate('overview.quality_gate.conditions.non_cayc.warning.link')}
+ </Link>
+ ),
+ }}
+ />
+ </p>
+ ) : (
+ <p className="big-spacer-top big-spacer-bottom">
+ {translate('overview.quality_gate.conditions.cayc.details')}
+ </p>
)}
<DocLink to="/user-guide/clean-as-you-code/">
--- /dev/null
+/*
+ * 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 { flatMap } from 'lodash';
import * as React from 'react';
-import Link from '../../../components/common/Link';
import HelpTooltip from '../../../components/controls/HelpTooltip';
-import QualifierIcon from '../../../components/icons/QualifierIcon';
import { Alert } from '../../../components/ui/Alert';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
-import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { getProjectQueryUrl } from '../../../helpers/urls';
import { ComponentQualifier, isApplication } from '../../../types/component';
import { QualityGateStatus } from '../../../types/quality-gates';
-import { Component } from '../../../types/types';
+import { CaycStatus, Component } from '../../../types/types';
import SonarLintPromotion from '../components/SonarLintPromotion';
+import ApplicationNonCaycProjectWarning from './ApplicationNonCaycProjectWarning';
import QualityGatePanelSection from './QualityGatePanelSection';
export interface QualityGatePanelProps {
const nonCaycProjectsInApp = isApplication(component.qualifier)
? qgStatuses
- .filter(({ isCaycCompliant }) => !isCaycCompliant)
+ .filter(({ caycStatus }) => caycStatus === CaycStatus.NonCompliant)
+ .sort(({ name: a }, { name: b }) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
+ : [];
+
+ const overCompliantCaycProjectsInApp = isApplication(component.qualifier)
+ ? qgStatuses
+ .filter(({ caycStatus }) => caycStatus === CaycStatus.OverCompliant)
.sort(({ name: a }, { name: b }) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
: [];
</div>
{(overallFailedConditionsCount > 0 ||
- qgStatuses.some(({ isCaycCompliant }) => !isCaycCompliant)) && (
+ qgStatuses.some(({ caycStatus }) => caycStatus !== CaycStatus.Compliant)) && (
<div data-test="overview__quality-gate-conditions">
{qgStatuses.map((qgStatus) => (
<QualityGatePanelSection
)}
{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,
QualityGateStatusConditionEnhanced,
} from '../../../types/quality-gates';
-import { Component } from '../../../types/types';
+import { CaycStatus, Component } from '../../../types/types';
import QualityGateConditions from '../components/QualityGateConditions';
import CleanAsYouCodeWarning from './CleanAsYouCodeWarning';
+import CleanAsYouCodeWarningOverCompliant from './CleanAsYouCodeWarningOverCompliant';
export interface QualityGatePanelSectionProps {
branchLike?: BranchLike;
if (
!(
qgStatus.failedConditions.length > 0 ||
- (!qgStatus.isCaycCompliant && !isApplication(component.qualifier))
+ (qgStatus.caycStatus !== CaycStatus.Compliant && !isApplication(component.qualifier))
)
) {
return null;
const showSectionTitles =
isApplication(component.qualifier) ||
- !qgStatus.isCaycCompliant ||
+ qgStatus.caycStatus !== CaycStatus.Compliant ||
(overallFailedConditions.length > 0 && newCodeFailedConditions.length > 0);
const toggleLabel = 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 && (
<>
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey } from '../../../../types/metrics';
import { GraphType } from '../../../../types/project-activity';
-import { Measure, Metric } from '../../../../types/types';
+import { CaycStatus, Measure, Metric } from '../../../../types/types';
import BranchOverview, { BRANCH_OVERVIEW_ACTIVITY_GRAPH, NO_CI_DETECTED } from '../BranchOverview';
jest.mock('../../../../api/measures', () => {
jest
.mocked(getQualityGateProjectStatus)
.mockResolvedValueOnce(
- mockQualityGateProjectStatus({ status: 'OK', isCaycCompliant: false })
+ mockQualityGateProjectStatus({ status: 'OK', caycStatus: CaycStatus.NonCompliant })
);
renderBranchOverview();
it("should show projects that don't have a compliant quality gate", async () => {
const appStatus = mockQualityGateApplicationStatus({
projects: [
- { key: '1', name: 'first project', conditions: [], isCaycCompliant: false, status: 'OK' },
- { key: '2', name: 'second', conditions: [], isCaycCompliant: true, status: 'OK' },
- { key: '3', name: 'number 3', conditions: [], isCaycCompliant: false, status: 'OK' },
+ {
+ key: '1',
+ name: 'first project',
+ conditions: [],
+ caycStatus: CaycStatus.NonCompliant,
+ status: 'OK',
+ },
+ {
+ key: '2',
+ name: 'second',
+ conditions: [],
+ caycStatus: CaycStatus.Compliant,
+ status: 'OK',
+ },
+ {
+ key: '3',
+ name: 'number 3',
+ conditions: [],
+ caycStatus: CaycStatus.NonCompliant,
+ status: 'OK',
+ },
{
key: '4',
name: 'four',
errorThreshold: '0',
},
],
- isCaycCompliant: false,
+ caycStatus: CaycStatus.NonCompliant,
status: 'ERROR',
},
],
} from '../../../../helpers/mocks/quality-gates';
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey } from '../../../../types/metrics';
+import { CaycStatus } from '../../../../types/types';
import { QualityGatePanelSection, QualityGatePanelSectionProps } from '../QualityGatePanelSection';
it('should render correctly', () => {
qgStatus: mockQualityGateStatus({
failedConditions: [],
status: 'OK',
+ caycStatus: CaycStatus.Compliant,
}),
}).type()
).toBeNull();
mockQualityGateStatusConditionEnhanced({ metric: MetricKey.new_bugs }),
],
status: 'ERROR',
- isCaycCompliant: false,
+ caycStatus: CaycStatus.NonCompliant,
})}
{...props}
/>
key="foo"
qgStatus={
{
+ "caycStatus": "compliant",
"failedConditions": [
{
"actual": "10",
},
],
"ignoredConditions": false,
- "isCaycCompliant": true,
"key": "foo",
"name": "Foo",
"status": "ERROR",
key="foo"
qgStatus={
{
+ "caycStatus": "compliant",
"failedConditions": [
{
"actual": "10",
},
],
"ignoredConditions": false,
- "isCaycCompliant": true,
"key": "foo",
"name": "Foo",
"status": "ERROR",
key="foo"
qgStatus={
{
+ "caycStatus": "compliant",
"failedConditions": [
{
"actual": "10",
},
],
"ignoredConditions": false,
- "isCaycCompliant": true,
"key": "foo",
"name": "Foo",
"status": "ERROR",
key="foo"
qgStatus={
{
+ "caycStatus": "compliant",
"failedConditions": [],
"ignoredConditions": false,
- "isCaycCompliant": true,
"key": "foo",
"name": "Foo",
"status": "OK",
key="foo"
qgStatus={
{
+ "caycStatus": "compliant",
"failedConditions": [
{
"actual": "10",
},
],
"ignoredConditions": false,
- "isCaycCompliant": true,
"key": "foo",
"name": "Foo",
"status": "ERROR",
key="foo"
qgStatus={
{
+ "caycStatus": "compliant",
"failedConditions": [
{
"actual": "10",
},
],
"ignoredConditions": true,
- "isCaycCompliant": true,
"key": "foo",
"name": "Foo",
"status": "ERROR",
<Memo(QualityGateConditions)
component={
{
+ "caycStatus": "non-compliant",
"failedConditions": [
{
"actual": "10",
},
],
"ignoredConditions": false,
- "isCaycCompliant": false,
"key": "foo",
"name": "Foo",
"status": "ERROR",
<Memo(QualityGateConditions)
component={
{
+ "caycStatus": "non-compliant",
"failedConditions": [
{
"actual": "10",
},
],
"ignoredConditions": false,
- "isCaycCompliant": false,
"key": "foo",
"name": "Foo",
"status": "ERROR",
<Memo(QualityGateConditions)
component={
{
+ "caycStatus": "non-compliant",
"failedConditions": [
{
"actual": "10",
},
],
"ignoredConditions": false,
- "isCaycCompliant": false,
"key": "foo",
"name": "Foo",
"status": "ERROR",
<Memo(QualityGateConditions)
component={
{
+ "caycStatus": "non-compliant",
"failedConditions": [
{
"actual": "10",
},
],
"ignoredConditions": false,
- "isCaycCompliant": false,
"key": "foo",
"name": "Foo",
"status": "ERROR",
--- /dev/null
+/*
+ * 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 { deleteCondition } from '../../../api/quality-gates';
import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
+import { colors } from '../../../app/theme';
import { DeleteButton, EditButton } from '../../../components/controls/buttons';
import ConfirmModal from '../../../components/controls/ConfirmModal';
+import Tooltip from '../../../components/controls/Tooltip';
+import InfoIcon from '../../../components/icons/InfoIcon';
import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n';
-import { Condition as ConditionType, Dict, Metric, QualityGate } from '../../../types/types';
-import { CAYC_CONDITIONS_WITHOUT_FIXED_VALUE, getLocalizedMetricNameNoDiffMetric } from '../utils';
+import {
+ CaycStatus,
+ Condition as ConditionType,
+ Dict,
+ Metric,
+ QualityGate,
+} from '../../../types/types';
+import {
+ CAYC_CONDITIONS_WITH_FIXED_VALUE,
+ getLocalizedMetricNameNoDiffMetric,
+ isCaycCondition,
+} from '../utils';
+import CaycOverCompliantBadgeTooltip from './CaycOverCompliantBadgeTooltip';
import ConditionModal from './ConditionModal';
import ConditionValue from './ConditionValue';
modal: boolean;
}
+const TOOLTIP_MOUSE_LEAVE_DELAY = 0.3;
export class ConditionComponent extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
isCaycModal = false,
} = this.props;
+ const isCaycCompliantAndOverCompliant = qualityGate.caycStatus !== CaycStatus.NonCompliant;
+
return (
<tr className={classNames({ highlighted: updated })}>
<td className="text-middle">
metric={metric}
isCaycModal={isCaycModal}
condition={condition}
- isCaycCompliant={qualityGate.isCaycCompliant}
+ isCaycCompliantAndOverCompliant={isCaycCompliantAndOverCompliant}
/>
</td>
<td className="text-middle nowrap display-flex-justify-end">
+ {!isCaycCondition(condition) && isCaycCompliantAndOverCompliant && (
+ <span className="display-flex-center spacer-right">
+ <Tooltip
+ overlay={<CaycOverCompliantBadgeTooltip />}
+ mouseLeaveDelay={TOOLTIP_MOUSE_LEAVE_DELAY}
+ >
+ <InfoIcon fill={colors.alertIconInfo} />
+ </Tooltip>
+ </span>
+ )}
{!isCaycModal && canEdit && (
<>
- {(!qualityGate.isCaycCompliant ||
- CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(condition.metric) ||
- (qualityGate.isCaycCompliant && showEdit)) && (
+ {(!isCaycCompliantAndOverCompliant ||
+ !CAYC_CONDITIONS_WITH_FIXED_VALUE.includes(condition.metric) ||
+ (isCaycCompliantAndOverCompliant && showEdit)) && (
<>
<EditButton
aria-label={translateWithParameters(
)}
</>
)}
- {(!qualityGate.isCaycCompliant || (qualityGate.isCaycCompliant && showEdit)) && (
+ {(!isCaycCompliantAndOverCompliant ||
+ !isCaycCondition(condition) ||
+ (isCaycCompliantAndOverCompliant && showEdit)) && (
<>
<DeleteButton
aria-label={translateWithParameters(
import { sortBy } from 'lodash';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { createCondition, deleteCondition, updateCondition } from '../../../api/quality-gates';
+import { createCondition, updateCondition } from '../../../api/quality-gates';
import DocLink from '../../../components/common/DocLink';
import ConfirmModal from '../../../components/controls/ConfirmModal';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Condition, Dict, Metric, QualityGate } from '../../../types/types';
-import {
- getCaycConditionsWithCorrectValue,
- getCorrectCaycCondition,
- getWeakMissingAndNonCaycConditions,
-} from '../utils';
+import { getCorrectCaycCondition, getWeakMissingAndNonCaycConditions } from '../utils';
import ConditionsTable from './ConditionsTable';
interface Props {
updateCaycQualityGate = () => {
const { conditions, qualityGate } = this.props;
const promiseArr: Promise<Condition | undefined | void>[] = [];
- const { weakConditions, missingConditions, nonCaycConditions } =
- getWeakMissingAndNonCaycConditions(conditions);
+ const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions);
weakConditions.forEach((condition) => {
promiseArr.push(
);
});
- nonCaycConditions.forEach((condition) => {
- promiseArr.push(
- deleteCondition({ id: condition.id })
- .then(() => this.props.onRemoveCondition(condition))
- .catch(() => undefined)
- );
- });
-
return Promise.all(promiseArr).then(() => {
this.props.lockEditing();
});
render() {
const { conditions, qualityGate, metrics } = this.props;
- const caycConditionsWithCorrectValue = getCaycConditionsWithCorrectValue(conditions);
- const sortedConditions = sortBy(
- caycConditionsWithCorrectValue,
+
+ const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions);
+ const sortedWeakConditions = sortBy(
+ weakConditions,
+ (condition) => metrics[condition.metric] && metrics[condition.metric].name
+ );
+
+ const sortedMissingConditions = sortBy(
+ missingConditions,
(condition) => metrics[condition.metric] && metrics[condition.metric].name
);
+
return (
<ConfirmModal
header={translateWithParameters(
}}
/>
</p>
- <p className="big-spacer-top big-spacer-bottom">
+
+ {sortedMissingConditions.length > 0 && (
+ <>
+ <h4 className="big-spacer-top spacer-bottom">
+ {translateWithParameters(
+ 'quality_gates.cayc.review_update_modal.add_condition.header',
+ sortedMissingConditions.length
+ )}
+ </h4>
+ <ConditionsTable
+ {...this.props}
+ conditions={sortedMissingConditions}
+ showEdit={false}
+ isCaycModal={true}
+ />
+ </>
+ )}
+
+ {sortedWeakConditions.length > 0 && (
+ <>
+ <h4 className="big-spacer-top spacer-bottom">
+ {translateWithParameters(
+ 'quality_gates.cayc.review_update_modal.modify_condition.header',
+ sortedWeakConditions.length
+ )}
+ </h4>
+ <ConditionsTable
+ {...this.props}
+ conditions={sortedWeakConditions}
+ showEdit={false}
+ isCaycModal={true}
+ />
+ </>
+ )}
+
+ <h4 className="big-spacer-top spacer-bottom">
{translate('quality_gates.cayc.review_update_modal.description2')}
- </p>
- <h3 className="medium text-normal spacer-top spacer-bottom">
- {translate('quality_gates.conditions.new_code', 'long')}
- </h3>
- <ConditionsTable
- {...this.props}
- conditions={sortedConditions}
- showEdit={false}
- isCaycModal={true}
- />
+ </h4>
</div>
</ConfirmModal>
);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import classNames from 'classnames';
import * as React from 'react';
import { formatMeasure } from '../../../helpers/measures';
import { Condition, Metric } from '../../../types/types';
condition: Condition;
isCaycModal?: boolean;
metric: Metric;
- isCaycCompliant?: boolean;
+ isCaycCompliantAndOverCompliant?: boolean;
}
-function ConditionValue({ condition, isCaycModal, metric, isCaycCompliant }: Props) {
+function ConditionValue({
+ condition,
+ isCaycModal,
+ metric,
+ isCaycCompliantAndOverCompliant,
+}: Props) {
if (isCaycModal) {
+ const isToBeModified = condition.error !== getCorrectCaycCondition(condition).error;
+
return (
<>
- <span className="spacer-right">
+ {isToBeModified && (
+ <span className="red-text strike-through spacer-right">
+ {formatMeasure(condition.error, metric.type)}
+ </span>
+ )}
+ <span className={classNames('spacer-right', { 'green-text': isToBeModified })}>
{formatMeasure(getCorrectCaycCondition(condition).error, metric.type)}
</span>
- <ConditionValueDescription condition={getCorrectCaycCondition(condition)} metric={metric} />
+ <ConditionValueDescription
+ className={classNames({ 'green-text': isToBeModified })}
+ condition={getCorrectCaycCondition(condition)}
+ metric={metric}
+ />
</>
);
}
return (
<>
<span className="spacer-right">{formatMeasure(condition.error, metric.type)}</span>
- {isCaycCompliant && isCaycCondition(condition) && (
+ {isCaycCompliantAndOverCompliant && isCaycCondition(condition) && (
<ConditionValueDescription condition={condition} metric={metric} />
)}
</>
import { isDiffMetric } from '../../../helpers/measures';
import { Feature } from '../../../types/features';
import { MetricKey } from '../../../types/metrics';
-import { Condition as ConditionType, Dict, Metric, QualityGate } from '../../../types/types';
+import {
+ CaycStatus,
+ Condition as ConditionType,
+ Dict,
+ Metric,
+ QualityGate,
+} from '../../../types/types';
import ConditionModal from './ConditionModal';
import CaycReviewUpdateConditionsModal from './ConditionReviewAndUpdateModal';
import ConditionsTable from './ConditionsTable';
constructor(props: Props) {
super(props);
this.state = {
- unlockEditing: !props.qualityGate.isCaycCompliant,
+ unlockEditing: props.qualityGate.caycStatus === CaycStatus.NonCompliant,
};
}
componentDidUpdate(prevProps: Readonly<Props>): void {
const { qualityGate } = this.props;
if (prevProps.qualityGate.name !== qualityGate.name) {
- this.setState({ unlockEditing: !qualityGate.isCaycCompliant });
+ this.setState({ unlockEditing: qualityGate.caycStatus === CaycStatus.NonCompliant });
}
}
return (
<div className="quality-gate-section">
- {qualityGate.isCaycCompliant && (
+ {qualityGate.caycStatus !== CaycStatus.NonCompliant && (
<Alert className="big-spacer-top big-spacer-bottom cayc-success-banner" variant="success">
<h4 className="spacer-bottom cayc-success-header">
{translate('quality_gates.cayc.banner.title')}
</DocLink>
),
new_code_link: (
- <DocLink to="/project-administration/defining-new-code//">
+ <DocLink to="/project-administration/defining-new-code/">
{translate('quality_gates.cayc.new_code')}
</DocLink>
),
</Alert>
)}
- {!qualityGate.isCaycCompliant && (
+ {qualityGate.caycStatus === CaycStatus.OverCompliant && (
+ <Alert className="big-spacer-top big-spacer-bottom cayc-success-banner" variant="info">
+ <h4 className="spacer-bottom cayc-over-compliant-header">
+ {translate('quality_gates.cayc_over_compliant.banner.title')}
+ </h4>
+ <p>{translate('quality_gates.cayc_over_compliant.banner.description1')}</p>
+ <div className="cayc-warning-description spacer-top">
+ <FormattedMessage
+ id="quality_gates.cayc_over_compliant.banner.description2"
+ defaultMessage={translate('quality_gates.cayc_over_compliant.banner.description2')}
+ values={{
+ link: (
+ <DocLink to="/user-guide/clean-as-you-code/#potential-drawbacks">
+ {translate('quality_gates.cayc_over_compliant.banner.link')}
+ </DocLink>
+ ),
+ }}
+ />
+ </div>
+ </Alert>
+ )}
+
+ {qualityGate.caycStatus === CaycStatus.NonCompliant && (
<Alert className="big-spacer-top big-spacer-bottom" variant="warning">
<h4 className="spacer-bottom cayc-warning-header">
{translate('quality_gates.cayc_missing.banner.title')}
</Alert>
)}
- {(!qualityGate.isCaycCompliant || (qualityGate.isCaycCompliant && unlockEditing)) &&
- canEdit && (
- <div className="pull-right">
- <ModalButton modal={this.renderConditionModal}>
- {({ onClick }) => (
- <Button data-test="quality-gates__add-condition" onClick={onClick}>
- {translate('quality_gates.add_condition')}
- </Button>
- )}
- </ModalButton>
- </div>
- )}
+ {(qualityGate.caycStatus === CaycStatus.NonCompliant || unlockEditing) && canEdit && (
+ <div className="pull-right">
+ <ModalButton modal={this.renderConditionModal}>
+ {({ onClick }) => (
+ <Button data-test="quality-gates__add-condition" onClick={onClick}>
+ {translate('quality_gates.add_condition')}
+ </Button>
+ )}
+ </ModalButton>
+ </div>
+ )}
<header className="display-flex-center">
<h2 className="big">{translate('quality_gates.conditions')}</h2>
</div>
)}
- {qualityGate.isCaycCompliant && !unlockEditing && canEdit && (
+ {qualityGate.caycStatus !== CaycStatus.NonCompliant && !unlockEditing && canEdit && (
<div className="big-spacer-top big-spacer-bottom cayc-warning-description it__qg-unfollow-cayc">
<p>
<FormattedMessage
addGlobalSuccessMessage(translate('quality_gates.condition_added'));
const updatedQualityGate = addCondition(clone(qualityGate), condition);
- if (qualityGate.isCaycCompliant !== updatedQualityGate.isCaycCompliant) {
+ if (qualityGate.caycStatus !== updatedQualityGate.caycStatus) {
this.props.refreshQualityGates();
}
}
addGlobalSuccessMessage(translate('quality_gates.condition_updated'));
const updatedQualityGate = replaceCondition(clone(qualityGate), newCondition, oldCondition);
- if (qualityGate.isCaycCompliant !== updatedQualityGate.isCaycCompliant) {
+ if (qualityGate.caycStatus !== updatedQualityGate.caycStatus) {
this.props.refreshQualityGates();
}
return {
}
addGlobalSuccessMessage(translate('quality_gates.condition_deleted'));
const updatedQualityGate = deleteCondition(clone(qualityGate), condition);
- if (qualityGate.isCaycCompliant !== updatedQualityGate.isCaycCompliant) {
+ if (qualityGate.caycStatus !== updatedQualityGate.caycStatus) {
this.props.refreshQualityGates();
}
return {
import Tooltip from '../../../components/controls/Tooltip';
import AlertWarnIcon from '../../../components/icons/AlertWarnIcon';
import { translate } from '../../../helpers/l10n';
-import { QualityGate } from '../../../types/types';
+import { CaycStatus, QualityGate } from '../../../types/types';
import BuiltInQualityGateBadge from './BuiltInQualityGateBadge';
import CaycBadgeTooltip from './CaycBadgeTooltip';
import CopyQualityGateForm from './CopyQualityGateForm';
render() {
const { qualityGate } = this.props;
const actions = qualityGate.actions || ({} as any);
- const { isCaycCompliant } = qualityGate;
return (
<div className="layout-page-header-panel layout-page-main-header issues-main-header">
<div className="pull-left display-flex-center">
<h2>{qualityGate.name}</h2>
{qualityGate.isBuiltIn && <BuiltInQualityGateBadge className="spacer-left" />}
- {!qualityGate.isCaycCompliant && (
+ {qualityGate.caycStatus === CaycStatus.NonCompliant && (
<Tooltip overlay={<CaycBadgeTooltip />} mouseLeaveDelay={TOOLTIP_MOUSE_LEAVE_DELAY}>
<AlertWarnIcon className="spacer-left" />
</Tooltip>
{({ onClick }) => (
<Tooltip
overlay={
- !isCaycCompliant ? translate('quality_gates.cannot_copy_no_cayc') : null
+ qualityGate.caycStatus === CaycStatus.NonCompliant
+ ? translate('quality_gates.cannot_copy_no_cayc')
+ : null
}
accessible={false}
>
className="little-spacer-left"
id="quality-gate-copy"
onClick={onClick}
- disabled={!isCaycCompliant}
+ disabled={qualityGate.caycStatus === CaycStatus.NonCompliant}
>
{translate('copy')}
</Button>
{actions.setAsDefault && (
<Tooltip
overlay={
- !isCaycCompliant ? translate('quality_gates.cannot_set_default_no_cayc') : null
+ qualityGate.caycStatus === CaycStatus.NonCompliant
+ ? translate('quality_gates.cannot_set_default_no_cayc')
+ : null
}
accessible={false}
>
<Button
className="little-spacer-left"
- disabled={!isCaycCompliant}
+ disabled={qualityGate.caycStatus === CaycStatus.NonCompliant}
id="quality-gate-toggle-default"
onClick={this.handleSetAsDefaultClick}
>
import AlertWarnIcon from '../../../components/icons/AlertWarnIcon';
import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls';
-import { QualityGate } from '../../../types/types';
+import { CaycStatus, QualityGate } from '../../../types/types';
import BuiltInQualityGateBadge from './BuiltInQualityGateBadge';
interface Props {
)}
{qualityGate.isBuiltIn && <BuiltInQualityGateBadge className="little-spacer-left" />}
- {!qualityGate.isCaycCompliant && (
+ {qualityGate.caycStatus === CaycStatus.NonCompliant && (
<>
{/* Adding a11y-hidden span for accessibility */}
<span className="a11y-hidden">{translate('quality_gates.cayc.tooltip.message')}</span>
expect(conditionsWrapper.getAllByText('Coverage')).toHaveLength(2); // This quality gate has duplicate condition
expect(conditionsWrapper.getByText('Duplicated Lines (%)')).toBeInTheDocument();
- expect(screen.queryByTestId('quality-gates__conditions-overall')).not.toBeInTheDocument();
+ const overallConditionsWrapper = within(
+ await screen.findByTestId('quality-gates__conditions-overall')
+ );
+ expect(overallConditionsWrapper.getByText('Complexity / Function')).toBeInTheDocument();
});
it('should show success banner when quality gate is CAYC compliant', async () => {
expect(await conditionsWrapper.findByText('Duplicated Lines (%)')).toBeInTheDocument();
});
+it('should show info banner when quality gate is CAYC over-compliant', async () => {
+ const user = userEvent.setup();
+ handler.setIsAdmin(true);
+ renderQualityGateApp();
+
+ const qualityGate = await screen.findByText('Over Compliant CAYC QG');
+
+ await user.click(qualityGate);
+
+ expect(screen.getByText('quality_gates.cayc.banner.title')).toBeInTheDocument();
+ expect(screen.getByText('quality_gates.cayc.banner.description')).toBeInTheDocument();
+ expect(screen.getByText('quality_gates.cayc_over_compliant.banner.title')).toBeInTheDocument();
+ expect(
+ screen.queryByText('quality_gates.cayc_condition.missing_warning.title')
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByRole('button', { name: 'quality_gates.cayc_condition.review_update' })
+ ).not.toBeInTheDocument();
+ expect(screen.getByText('quality_gates.cayc.unlock_edit')).toBeInTheDocument();
+});
+
it('should unlock editing option for CAYC conditions', async () => {
const user = userEvent.setup();
handler.setIsAdmin(true);
color: var(--alertTextSuccess);
}
+.cayc-over-compliant-header {
+ color: var(--veryDarkBlue);
+}
+
.cayc-warning-description {
line-height: 18px;
}
+
+.red-text {
+ color: var(--red);
+}
+
+.green-text {
+ color: var(--success500);
+}
+
+.strike-through {
+ text-decoration: line-through;
+}
*/
import { getLocalizedMetricName } from '../../helpers/l10n';
import { isDiffMetric } from '../../helpers/measures';
-import { Condition, Dict, Metric, QualityGate } from '../../types/types';
+import { CaycStatus, Condition, Dict, Metric, QualityGate } from '../../types/types';
const CAYC_CONDITIONS: { [key: string]: Condition } = {
new_reliability_rating: {
};
export const CAYC_CONDITIONS_WITHOUT_FIXED_VALUE = ['new_duplicated_lines_density', 'new_coverage'];
+export const CAYC_CONDITIONS_WITH_FIXED_VALUE = [
+ 'new_security_hotspots_reviewed',
+ 'new_maintainability_rating',
+ 'new_security_rating',
+ 'new_reliability_rating',
+];
export function isCaycCondition(condition: Condition) {
return condition.metric in CAYC_CONDITIONS;
const result: {
weakConditions: Condition[];
missingConditions: Condition[];
- nonCaycConditions: Condition[];
} = {
weakConditions: [],
missingConditions: [],
- nonCaycConditions: [],
};
Object.keys(CAYC_CONDITIONS).forEach((key) => {
const selectedCondition = conditions.find((condition) => condition.metric === key);
result.weakConditions.push(selectedCondition);
}
});
-
- result.nonCaycConditions = getNonCaycConditions(conditions);
return result;
}
});
}
-export function getNonCaycConditions(conditions: Condition[]) {
- return conditions.filter((condition) => !isCaycCondition(condition));
-}
-
export function getCorrectCaycCondition(condition: Condition) {
if (CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(condition.metric)) {
return condition;
const oldConditions = qualityGate.conditions || [];
const conditions = [...oldConditions, condition];
if (conditions) {
- qualityGate.isCaycCompliant = updateCaycComplaintStatus(conditions);
+ qualityGate.caycStatus = updateCaycCompliantStatus(conditions);
}
return { ...qualityGate, conditions };
}
const conditions =
qualityGate.conditions && qualityGate.conditions.filter((candidate) => candidate !== condition);
if (conditions) {
- qualityGate.isCaycCompliant = updateCaycComplaintStatus(conditions);
+ qualityGate.caycStatus = updateCaycCompliantStatus(conditions);
}
return { ...qualityGate, conditions };
}
return candidate === oldCondition ? newCondition : candidate;
});
if (conditions) {
- qualityGate.isCaycCompliant = updateCaycComplaintStatus(conditions);
+ qualityGate.caycStatus = updateCaycCompliantStatus(conditions);
}
return { ...qualityGate, conditions };
}
-export function updateCaycComplaintStatus(conditions: Condition[]) {
- if (conditions.length !== Object.keys(CAYC_CONDITIONS).length) {
- return false;
+export function updateCaycCompliantStatus(conditions: Condition[]) {
+ if (conditions.length < Object.keys(CAYC_CONDITIONS).length) {
+ return CaycStatus.NonCompliant;
}
for (const key of Object.keys(CAYC_CONDITIONS)) {
const selectedCondition = conditions.find((condition) => condition.metric === key);
if (!selectedCondition) {
- return false;
+ return CaycStatus.NonCompliant;
}
if (
selectedCondition &&
selectedCondition.error !== CAYC_CONDITIONS[key].error
) {
- return false;
+ return CaycStatus.NonCompliant;
}
}
- return true;
+ if (conditions.length > Object.keys(CAYC_CONDITIONS).length) {
+ return CaycStatus.OverCompliant;
+ }
+
+ return CaycStatus.Compliant;
}
export function getPossibleOperators(metric: Metric) {
QualityGateStatusCondition,
QualityGateStatusConditionEnhanced,
} from '../../types/quality-gates';
-import { QualityGate } from '../../types/types';
+import { CaycStatus, QualityGate } from '../../types/types';
import { mockMeasureEnhanced, mockMetric } from '../testMocks';
export function mockQualityGate(overrides: Partial<QualityGate> = {}): QualityGate {
): QualityGateStatus {
return {
ignoredConditions: false,
- isCaycCompliant: true,
+ caycStatus: CaycStatus.Compliant,
failedConditions: [mockQualityGateStatusConditionEnhanced()],
key: 'foo',
name: 'Foo',
},
],
ignoredConditions: false,
- isCaycCompliant: true,
+ caycStatus: CaycStatus.Compliant,
status: 'OK',
...overrides,
};
value: '5',
},
],
- isCaycCompliant: true,
+ caycStatus: CaycStatus.Compliant,
status: 'ERROR',
},
{
value: '15',
},
],
- isCaycCompliant: true,
+ caycStatus: CaycStatus.Compliant,
status: 'ERROR',
},
],
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { BranchLike } from './branch-like';
-import { MeasureEnhanced, Metric, Status } from './types';
+import { CaycStatus, MeasureEnhanced, Metric, Status } from './types';
import { UserBase } from './users';
export interface QualityGateProjectStatus {
conditions?: QualityGateProjectStatusCondition[];
ignoredConditions: boolean;
status: Status;
- isCaycCompliant: boolean;
+ caycStatus: CaycStatus;
}
export interface QualityGateProjectStatusCondition {
key: string;
name: string;
status: Status;
- isCaycCompliant: boolean;
+ caycStatus: CaycStatus;
}
export interface QualityGateStatus {
failedConditions: QualityGateStatusConditionEnhanced[];
ignoredConditions?: boolean;
- isCaycCompliant: boolean;
+ caycStatus: CaycStatus;
key: string;
name: string;
status: Status;
url: string;
}
+export enum CaycStatus {
+ Compliant = 'compliant',
+ NonCompliant = 'non-compliant',
+ OverCompliant = 'over-compliant',
+}
+
export interface QualityGate {
actions?: {
associateProjects?: boolean;
};
conditions?: Condition[];
isBuiltIn?: boolean;
- isCaycCompliant?: boolean;
+ caycStatus?: CaycStatus;
isDefault?: boolean;
name: string;
}
for (QualityGateDto qualityGateDto : qualityGateDtos) {
qualityGates.add(
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.entry;
import static org.assertj.core.groups.Tuple.tuple;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.sonar.db.component.BranchType.BRANCH;
import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_CPP_KEY;
import static org.sonar.server.metric.UnanalyzedLanguageMetrics.UNANALYZED_C_KEY;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
import static org.sonar.server.telemetry.TelemetryDataLoaderImpl.SCIM_PROPERTY_ENABLED;
@RunWith(DataProviderRunner.class)
public void setUpBuiltInQualityGate() {
String builtInQgName = "Sonar Way";
builtInDefaultQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName(builtInQgName).setBuiltIn(true));
+ when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(NON_COMPLIANT);
db.qualityGates().setDefaultQualityGate(builtInDefaultQualityGate);
bugsDto = db.measures().insertMetric(m -> m.setKey(BUGS_KEY));
tuple(1L, 0L, qualityGate1.getUuid(), "scm-1", "ci-1", "azure_devops_cloud", Optional.of(1L), Optional.of(1L), Optional.of(1L), Optional.of(50L), Optional.of(5L)),
tuple(1L, 0L, builtInDefaultQualityGate.getUuid(), "scm-2", "ci-2", "github_cloud", Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()));
assertThat(data.getQualityGates())
- .extracting(TelemetryData.QualityGate::uuid, TelemetryData.QualityGate::isCaycCompliant)
+ .extracting(TelemetryData.QualityGate::uuid, TelemetryData.QualityGate::caycStatus)
.containsExactlyInAnyOrder(
- tuple(builtInDefaultQualityGate.getUuid(), false),
- tuple(qualityGate1.getUuid(), false),
- tuple(qualityGate2.getUuid(), false)
+ tuple(builtInDefaultQualityGate.getUuid(), "non-compliant"),
+ tuple(qualityGate1.getUuid(), "non-compliant"),
+ tuple(qualityGate2.getUuid(), "non-compliant")
);
}
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED;
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.OVER_COMPLIANT;
public class QualityGateCaycChecker {
this.dbClient = dbClient;
}
- public boolean checkCaycCompliant(DbSession dbSession, String qualityGateUuid) {
+ public QualityGateCaycStatus checkCaycCompliant(DbSession dbSession, String qualityGateUuid) {
var conditionsByMetricId = dbClient.gateConditionDao().selectForQualityGate(dbSession, qualityGateUuid)
.stream()
.collect(uniqueIndex(QualityGateConditionDto::getMetricUuid));
- if (conditionsByMetricId.size() != CAYC_METRICS.size()) {
- return false;
+ if (conditionsByMetricId.size() < CAYC_METRICS.size()) {
+ return NON_COMPLIANT;
}
var metrics = dbClient.metricDao().selectByUuids(dbSession, conditionsByMetricId.keySet())
.filter(metric -> checkMetricCaycCompliant(conditionsByMetricId.get(metric.getUuid()), metric))
.count();
- return count == CAYC_METRICS.size();
+ if (metrics.size() == count && count == CAYC_METRICS.size()) {
+ return COMPLIANT;
+ } else if (metrics.size() > count && count == CAYC_METRICS.size()) {
+ return OVER_COMPLIANT;
+ }
+
+ return NON_COMPLIANT;
}
- public boolean checkCaycCompliantFromProject(DbSession dbSession, String projectUuid) {
+ public QualityGateCaycStatus checkCaycCompliantFromProject(DbSession dbSession, String projectUuid) {
return Optional.ofNullable(dbClient.qualityGateDao().selectByProjectUuid(dbSession, projectUuid))
.or(() -> Optional.ofNullable(dbClient.qualityGateDao().selectDefault(dbSession)))
.map(qualityGate -> checkCaycCompliant(dbSession, qualityGate.getUuid()))
- .orElse(false);
+ .orElse(NON_COMPLIANT);
}
private static boolean checkMetricCaycCompliant(QualityGateConditionDto condition, MetricDto metric) {
--- /dev/null
+/*
+ * 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")
.setResponseExample(Resources.getResource(this.getClass(), "list-example.json"))
.setChangelog(
- new Change("9.9", "'isCaycCompliant' field is added on quality gate"),
+ new Change("9.9", "'caycStatus' field is added on quality gate"),
new Change("8.4", "Field 'id' in the response is deprecated. Format changes from integer to string."),
new Change("7.0", "'isDefault' field is added on quality gate"),
new Change("7.0", "'default' field on root level is deprecated"),
.setName(qualityGate.getName())
.setIsDefault(qualityGate.getUuid().equals(defaultUuid))
.setIsBuiltIn(qualityGate.isBuiltIn())
- .setIsCaycCompliant(qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid()))
+ .setCaycStatus(qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid()).toString())
.setActions(wsSupport.getActions(dbSession, qualityGate, defaultQualityGate))
.build())
.collect(toList()));
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.qualitygate.QualityGateCaycChecker;
+import org.sonar.server.qualitygate.QualityGateCaycStatus;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.KeyExamples;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse;
.setSince("5.3")
.setHandler(this)
.setChangelog(
- new Change("9.9", "'isCaycCompliant' field is added to the response"),
+ new Change("9.9", "'caycStatus' field is added to the response"),
new Change("9.5", "The 'Execute Analysis' permission also allows to access the endpoint"),
new Change("8.5", "The field 'periods' in the response is deprecated. Use 'period' instead"),
new Change("7.7", "The parameters 'branch' and 'pullRequest' were added"),
ProjectAndSnapshot projectAndSnapshot = getProjectAndSnapshot(dbSession, analysisId, projectUuid, projectKey, branchKey, pullRequestId);
checkPermission(projectAndSnapshot.project);
Optional<String> measureData = loadQualityGateDetails(dbSession, projectAndSnapshot, analysisId != null);
- boolean isCaycCompliant = qualityGateCaycChecker.checkCaycCompliantFromProject(dbSession, projectAndSnapshot.project.getUuid());
+ QualityGateCaycStatus caycStatus = qualityGateCaycChecker.checkCaycCompliantFromProject(dbSession, projectAndSnapshot.project.getUuid());
return ProjectStatusResponse.newBuilder()
- .setProjectStatus(new QualityGateDetailsFormatter(measureData.orElse(null), projectAndSnapshot.snapshotDto.orElse(null), isCaycCompliant).format())
+ .setProjectStatus(new QualityGateDetailsFormatter(measureData.orElse(null), projectAndSnapshot.snapshotDto.orElse(null), caycStatus).format())
.build();
}
import java.util.stream.StreamSupport;
import javax.annotation.Nullable;
import org.sonar.db.component.SnapshotDto;
+import org.sonar.server.qualitygate.QualityGateCaycStatus;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.NewCodePeriod;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Period;
public class QualityGateDetailsFormatter {
private final Optional<String> optionalMeasureData;
private final Optional<SnapshotDto> optionalSnapshot;
- private final boolean isCaycCompliant;
+ private final QualityGateCaycStatus caycStatus;
private final ProjectStatusResponse.ProjectStatus.Builder projectStatusBuilder;
- public QualityGateDetailsFormatter(@Nullable String measureData, @Nullable SnapshotDto snapshot, boolean isCaycCompliant) {
+ public QualityGateDetailsFormatter(@Nullable String measureData, @Nullable SnapshotDto snapshot, QualityGateCaycStatus caycStatus) {
this.optionalMeasureData = Optional.ofNullable(measureData);
this.optionalSnapshot = Optional.ofNullable(snapshot);
- this.isCaycCompliant = isCaycCompliant;
+ this.caycStatus = caycStatus;
this.projectStatusBuilder = ProjectStatusResponse.ProjectStatus.newBuilder();
}
ProjectStatusResponse.Status qualityGateStatus = measureLevelToQualityGateStatus(json.get("level").getAsString());
projectStatusBuilder.setStatus(qualityGateStatus);
- projectStatusBuilder.setIsCaycCompliant(isCaycCompliant);
+ projectStatusBuilder.setCaycStatus(caycStatus.toString());
formatIgnoredConditions(json);
formatConditions(json.getAsJsonArray("conditions"));
}
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() {
import org.sonar.db.qualitygate.QualityGateConditionDto;
import org.sonar.db.qualitygate.QualityGateDto;
import org.sonar.server.qualitygate.QualityGateCaycChecker;
+import org.sonar.server.qualitygate.QualityGateCaycStatus;
import org.sonar.server.qualitygate.QualityGateFinder;
import org.sonarqube.ws.Qualitygates.ShowWsResponse;
.setSince("4.3")
.setResponseExample(Resources.getResource(this.getClass(), "show-example.json"))
.setChangelog(
- new Change("9.9", "'isCaycCompliant' field is added to the response"),
+ new Change("9.9", "'caycStatus' field is added to the response"),
new Change("8.4", "Parameter 'id' is deprecated. Format changes from integer to string. Use 'name' instead."),
new Change("8.4", "Field 'id' in the response is deprecated."),
new Change("7.6", "'period' and 'warning' fields of conditions are removed from the response"),
Collection<QualityGateConditionDto> conditions = getConditions(dbSession, qualityGate);
Map<String, MetricDto> metricsByUuid = getMetricsByUuid(dbSession, conditions);
QualityGateDto defaultQualityGate = qualityGateFinder.getDefault(dbSession);
- boolean isCaycCompliant = qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid());
- writeProtobuf(buildResponse(dbSession, qualityGate, defaultQualityGate, conditions, metricsByUuid, isCaycCompliant), request, response);
+ QualityGateCaycStatus caycStatus = qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid());
+ writeProtobuf(buildResponse(dbSession, qualityGate, defaultQualityGate, conditions, metricsByUuid, caycStatus), request, response);
}
}
}
private ShowWsResponse buildResponse(DbSession dbSession, QualityGateDto qualityGate, QualityGateDto defaultQualityGate, Collection<QualityGateConditionDto> conditions,
- Map<String, MetricDto> metricsByUuid, boolean isCaycCompliant) {
+ Map<String, MetricDto> metricsByUuid, QualityGateCaycStatus caycStatus) {
return ShowWsResponse.newBuilder()
.setId(qualityGate.getUuid())
.setName(qualityGate.getName())
.setIsBuiltIn(qualityGate.isBuiltIn())
- .setIsCaycCompliant(isCaycCompliant)
+ .setCaycStatus(caycStatus.toString())
.addAllConditions(conditions.stream()
.map(toWsCondition(metricsByUuid))
.collect(toList()))
"projectStatus": {
"status": "ERROR",
"ignoredConditions": false,
- "isCaycCompliant": false,
+ "caycStatus": "non-compliant",
"conditions": [
{
"status": "ERROR",
import org.sonar.db.metric.MetricDto;
import org.sonar.db.qualitygate.QualityGateConditionDto;
-import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.sonar.api.measures.CoreMetrics.DUPLICATED_LINES;
import static org.sonar.api.measures.CoreMetrics.LINE_COVERAGE;
import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE;
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED;
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING;
import static org.sonar.server.qualitygate.QualityGateCaycChecker.CAYC_METRICS;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.OVER_COMPLIANT;
public class QualityGateCaycCheckerTest {
public void checkCaycCompliant() {
String qualityGateUuid = "abcd";
CAYC_METRICS.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue()));
- assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isTrue();
+ assertEquals(COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid));
}
@Test
// extra conditions outside of CAYC requirements
List.of(LINE_COVERAGE, DUPLICATED_LINES).forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue()));
- assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isFalse();
+ assertEquals(OVER_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid));
}
@Test
var metric = metrics.get(i);
insertCondition(metric, qualityGateUuid, idx == i ? metric.getWorstValue() : metric.getBestValue());
}
- assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isFalse();
+ assertEquals(NON_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid));
});
}
String qualityGateUuid = "abcd";
List.of(NEW_MAINTAINABILITY_RATING, NEW_RELIABILITY_RATING, NEW_SECURITY_HOTSPOTS_REVIEWED, NEW_DUPLICATED_LINES_DENSITY)
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue()));
- assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isFalse();
+ assertEquals(NON_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid));
}
@Test
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue()));
List.of(NEW_COVERAGE, NEW_DUPLICATED_LINES_DENSITY)
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getWorstValue()));
- assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isTrue();
+ assertEquals(COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid));
}
private void insertCondition(MetricDto metricDto, String qualityGateUuid, Double threshold) {
*/
package org.sonar.server.qualitygate.ws;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
import static org.mockito.Mockito.when;
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES;
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.OVER_COMPLIANT;
import static org.sonar.test.JsonAssert.assertJson;
import static org.sonarqube.ws.Qualitygates.ListWsResponse;
private final WsActionTester ws = new WsActionTester(new ListAction(db.getDbClient(),
new QualityGatesWsSupport(dbClient, userSession, TestComponentFinder.from(db)), qualityGateFinder, qualityGateCaycChecker));
+ @Before
+ public void setUp() {
+ when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(COMPLIANT);
+ }
+
@Test
public void list_quality_gates() {
QualityGateDto defaultQualityGate = db.qualityGates().insertQualityGate();
}
@Test
- public void test_isCaycCompliant_flag() {
+ public void test_caycStatus_flag() {
QualityGateDto qualityGate1 = db.qualityGates().insertQualityGate();
QualityGateDto qualityGate2 = db.qualityGates().insertQualityGate();
- when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate1.getUuid()))).thenReturn(true);
- when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate2.getUuid()))).thenReturn(false);
+ QualityGateDto qualityGate3 = db.qualityGates().insertQualityGate();
+ when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate1.getUuid()))).thenReturn(COMPLIANT);
+ when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate2.getUuid()))).thenReturn(NON_COMPLIANT);
+ when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate3.getUuid()))).thenReturn(OVER_COMPLIANT);
db.qualityGates().setDefaultQualityGate(qualityGate1);
.executeProtobuf(ListWsResponse.class);
assertThat(response.getQualitygatesList())
- .extracting(QualityGate::getId, QualityGate::getIsCaycCompliant)
+ .extracting(QualityGate::getId, QualityGate::getCaycStatus)
.containsExactlyInAnyOrder(
- tuple(qualityGate1.getUuid(), true),
- tuple(qualityGate2.getUuid(), false));
+ tuple(qualityGate1.getUuid(), COMPLIANT.toString()),
+ tuple(qualityGate2.getUuid(), NON_COMPLIANT.toString()),
+ tuple(qualityGate3.getUuid(), OVER_COMPLIANT.toString()));
}
@Test
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.metric.MetricDto;
import org.sonar.db.permission.GlobalPermission;
-import org.sonar.db.qualitygate.QualityGateDto;
import org.sonar.server.component.TestComponentFinder;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.sonar.db.measure.MeasureTesting.newLiveMeasure;
import static org.sonar.db.measure.MeasureTesting.newMeasureDto;
import static org.sonar.db.metric.MetricTesting.newMetricDto;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_ANALYSIS_ID;
import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_BRANCH;
import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_PROJECT_ID;
private final WsActionTester ws = new WsActionTester(new ProjectStatusAction(dbClient, TestComponentFinder.from(db), userSession, qualityGateCaycChecker));
+ @Before
+ public void setUp() {
+ when(qualityGateCaycChecker.checkCaycCompliantFromProject(any(), any())).thenReturn(NON_COMPLIANT);
+ }
+
@Test
public void test_definition() {
WebService.Action action = ws.getDef();
ComponentDto project = db.components().insertPrivateProject();
var qg = db.qualityGates().insertBuiltInQualityGate();
db.qualityGates().setDefaultQualityGate(qg);
- when(qualityGateCaycChecker.checkCaycCompliantFromProject(any(DbSession.class), eq(project.uuid()))).thenReturn(true);
+ when(qualityGateCaycChecker.checkCaycCompliantFromProject(any(DbSession.class), eq(project.uuid()))).thenReturn(COMPLIANT);
SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(project));
dbSession.commit();
userSession.addProjectPermission(UserRole.USER, project);
.setParam(PARAM_ANALYSIS_ID, snapshot.getUuid())
.executeProtobuf(ProjectStatusResponse.class);
- assertThat(result.getProjectStatus().getIsCaycCompliant()).isTrue();
+ assertEquals(COMPLIANT.toString(), result.getProjectStatus().getCaycStatus());
}
@Test
import java.io.IOException;
import java.util.List;
-import java.util.Optional;
import javax.annotation.Nullable;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
public class QualityGateDetailsFormatterTest {
ProjectStatus result = underTest.format();
assertThat(result.getStatus()).isEqualTo(ProjectStatusResponse.Status.ERROR);
- assertThat(result.getIsCaycCompliant()).isFalse();
+ assertEquals(NON_COMPLIANT.toString(), result.getCaycStatus());
// check conditions
assertThat(result.getConditionsCount()).isEqualTo(3);
List<ProjectStatusResponse.Condition> conditions = result.getConditionsList();
}
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;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForClassTypes.tuple;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES;
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES;
+import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT;
import static org.sonar.test.JsonAssert.assertJson;
import static org.sonarqube.ws.Qualitygates.Actions;
new ShowAction(db.getDbClient(), new QualityGateFinder(db.getDbClient()),
new QualityGatesWsSupport(db.getDbClient(), userSession, TestComponentFinder.from(db)), qualityGateCaycChecker));
+ @Before
+ public void setUp() {
+ when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(COMPLIANT);
+ }
+
@Test
public void show() {
QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
@Test
public void show_isCaycCompliant() {
QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
- when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate.getUuid()))).thenReturn(true);
+ when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate.getUuid()))).thenReturn(COMPLIANT);
db.qualityGates().setDefaultQualityGate(qualityGate);
ShowWsResponse response = ws.newRequest()
.setParam("name", qualityGate.getName())
.executeProtobuf(ShowWsResponse.class);
- assertThat(response.getIsCaycCompliant()).isTrue();
+ assertEquals(COMPLIANT.toString(), response.getCaycStatus());
}
@Test
quality_gates.cayc_condition.review_update=Review and Fix Quality Gate
quality_gates.cayc.review_update_modal.header=Fix "{0}" to comply with Clean as You Code
quality_gates.cayc.review_update_modal.confirm_text=Fix Quality Gate
-quality_gates.cayc.review_update_modal.description1=This quality gate will be updated to comply with {cayc_link}. Existing conditions on duplication and coverage on new code will be preserved. All non-compliant conditions will be deleted, inlcuding conditions on overall code.
-quality_gates.cayc.review_update_modal.description2=Please review the new quality gate conditions below.
+quality_gates.cayc.review_update_modal.description1=This quality gate will be updated to comply with {cayc_link}. Please review the changes below.
+quality_gates.cayc.review_update_modal.description2=All other conditions will be preserved
quality_gates.cayc.new_maintainability_rating.A=Technical debt ratio is less than {0}
quality_gates.cayc.new_maintainability_rating=Technical debt ratio is greater than {1}
quality_gates.cayc.new_reliability_rating.A=No bugs
quality_gates.cayc.unlock_edit=Unlock editing
quality_gates.cayc.tooltip.message=This quality gate does not comply with Clean as You Code.
quality_gates.cayc.badge.tooltip.learn_more=Learn more: Clean as You Code
+quality_gates.cayc_over_compliant.tooltip.message=Additional conditions may have potential drawbacks.
+quality_gates.cayc_over_compliant.badge.tooltip.learn_more=Learn more: Potential drawbacks
quality_gates.cayc.banner.title=This quality gate complies with Clean as You Code
quality_gates.cayc.banner.description=This quality gate complies with the {cayc_link} methodology, so that you benefit from the most efficient approach to delivering Clean Code. The quality gate ensures that the {new_code_link} you’re working on has:
quality_gates.cayc.banner.list_item1=No bugs
quality_gates.cayc.banner.list_item3=No security hotspots to review
quality_gates.cayc.banner.list_item4=A limited amount of code smells (i.e. low technical debt ratio)
quality_gates.cayc.banner.list_item5=A controlled level of duplication and coverage
-quality_gates.cayc_unfollow.description=Are you reconsidering {cayc_link}? We strongly recommend this methodology to achieve a Clean Code state. However, if you still wish to move out of this approach you may edit this quality gate.
+quality_gates.cayc_over_compliant.banner.title=This quality gate may have drawbacks
+quality_gates.cayc_over_compliant.banner.description1=This quality gate includes additional conditions that may have drawbacks.
+quality_gates.cayc_over_compliant.banner.description2=Learn more: {link}
+quality_gates.cayc_over_compliant.banner.link=Potential drawbacks
+quality_gates.cayc_unfollow.description=You may click unlock to edit this quality gate. Adding extra conditions to a compliant quality gate can result in drawbacks. Are you reconsidering {cayc_link}? We strongly recommend this methodology to achieve a Clean Code status.
+quality_gates.cayc.review_update_modal.add_condition.header= {0} condition(s) on New Code will be added
+quality_gates.cayc.review_update_modal.modify_condition.header= {0} condition(s) on New Code will be modified
#------------------------------------------------------------------------------
#
overview.quality_gate.conditions_on_new_code=Only conditions on new code that are defined in the Quality Gate are checked. See the {link} associated to the project for details.
overview.quality_gate.conditions.cayc.warning=The quality gate used by this project does not comply with Clean as You Code.
overview.quality_gate.conditions.cayc.details=Fixing this quality gate will help you achieve a Clean Code state.
-overview.quality_gate.conditions.cayc.review=Review Quality Gate
+overview.quality_gate.conditions.cayc.details_with_link=Fixing {link} will help you achieve a Clean Code state.
+overview.quality_gate.conditions.non_cayc.warning.link=this quality gate
+overview.quality_gate.conditions.cayc_over_compliant.warning.link=This quality gate
+overview.quality_gate.conditions.cayc_over_compliant.details=This quality gate is Clean as You Code compliant, but it includes additional conditions that may have drawbacks.
+overview.quality_gate.conditions.cayc_over_compliant.details_with_link={link} is Clean as You Code compliant, but it includes additional conditions that may have drawbacks.
overview.quality_gate.conditions.cayc.link=Learn more: Clean as You Code
+overview.quality_gate.conditions.cayc_over_compliant.link=Learn more: Potential drawbacks
overview.quality_gate.application.non_cayc.projects_x={0} project(s) in this application use a Quality Gate that does not comply with Clean as You Code
+overview.quality_gate.application.cayc_over_compliant.projects_x={0} project(s) in this application use a Clean as You Code compliant quality gate with extra conditions. This may result in drawbacks.
overview.quality_gate.show_project_conditions_x=Show failed conditions for project {0}
overview.quality_gate.hide_project_conditions_x=Hide failed conditions for project {0}
overview.quality_profiles=Quality Profiles used
repeated Period periods = 3;
optional bool ignoredConditions = 4;
optional NewCodePeriod period = 5;
- optional bool isCaycCompliant = 6;
+ optional string caycStatus = 6;
}
message Condition {
repeated Condition conditions = 3;
optional bool isBuiltIn = 4;
optional Actions actions = 5;
- optional bool isCaycCompliant = 6;
+ optional string caycStatus = 6;
message Condition {
optional string id = 1;
optional bool isDefault = 3;
optional bool isBuiltIn = 4;
optional Actions actions = 5;
- optional bool isCaycCompliant = 6;
+ optional string caycStatus = 6;
}
message RootActions {