).then(
(results) => {
if (this.mounted) {
- const qgStatuses = results.map(({ measures = [], project, projectBranchLike }) => {
- const { key, name, status, isCaycCompliant } = project;
- const conditions = extractStatusConditionsFromApplicationStatusChildProject(project);
- const failedConditions = this.getFailedConditions(conditions, measures);
-
- return {
- failedConditions,
- isCaycCompliant,
- key,
- name,
- status,
- branchLike: projectBranchLike,
- };
- });
+ const qgStatuses = results
+ .map(({ measures = [], project, projectBranchLike }) => {
+ const { key, name, status, isCaycCompliant } = project;
+ const conditions = extractStatusConditionsFromApplicationStatusChildProject(project);
+ const failedConditions = this.getFailedConditions(conditions, measures);
+
+ return {
+ failedConditions,
+ isCaycCompliant,
+ key,
+ name,
+ status,
+ branchLike: projectBranchLike,
+ };
+ })
+ .sort((a, b) => Math.sign(b.failedConditions.length - a.failedConditions.length));
this.setState({
loadingStatus: false,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
import Link from '../../../components/common/Link';
import { Alert } from '../../../components/ui/Alert';
import { translate } from '../../../helpers/l10n';
+import { getQualityGateUrl } from '../../../helpers/urls';
+import { Component } from '../../../types/types';
-export default function CleanAsYouCodeWarning() {
+interface Props {
+ component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>;
+}
+
+export default function CleanAsYouCodeWarning({ component }: Props) {
return (
<>
<Alert variant="warning">{translate('overview.quality_gate.conditions.cayc.warning')}</Alert>
<p className="big-spacer-top big-spacer-bottom">
- {translate('overview.quality_gate.conditions.cayc.details')}
+ {component.qualityGate ? (
+ <FormattedMessage
+ id="overview.quality_gate.conditions.cayc.details"
+ defaultMessage={translate('overview.quality_gate.conditions.cayc.details')}
+ values={{
+ link: (
+ <Link to={getQualityGateUrl(component.qualityGate.key)}>
+ {translate('overview.quality_gate.conditions.cayc.details.link')}
+ </Link>
+ ),
+ }}
+ />
+ ) : (
+ translate('overview.quality_gate.conditions.cayc.details.no_link')
+ )}
</p>
+
<Link
target="_blank"
to="https://docs.sonarqube.org/latest/user-guide/clean-as-you-code/#quality-gate"
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 { ComponentQualifier } from '../../../types/component';
+import { getProjectQueryUrl } from '../../../helpers/urls';
+import { ComponentQualifier, isApplication } from '../../../types/component';
import { QualityGateStatus } from '../../../types/quality-gates';
import { Component } from '../../../types/types';
import SonarLintPromotion from '../components/SonarLintPromotion';
import QualityGatePanelSection from './QualityGatePanelSection';
export interface QualityGatePanelProps {
- component: Pick<Component, 'key' | 'qualifier'>;
+ component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>;
loading?: boolean;
qgStatuses?: QualityGateStatus[];
}
0
);
+ const nonCaycProjectsInApp = isApplication(component.qualifier)
+ ? qgStatuses
+ .filter(({ isCaycCompliant }) => !isCaycCompliant)
+ .sort(({ name: a }, { name: b }) => a.localeCompare(b, undefined, { sensitivity: 'base' }))
+ : [];
+
const showIgnoredConditionWarning =
component.qualifier === ComponentQualifier.Project &&
qgStatuses.some((p) => Boolean(p.ignoredConditions));
))}
</div>
)}
+
+ {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>
+ )}
</>
)}
</div>
export interface QualityGatePanelSectionProps {
branchLike?: BranchLike;
- component: Pick<Component, 'key' | 'qualifier'>;
+ component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>;
qgStatus: QualityGateStatus;
}
setCollapsed(!collapsed);
}, [collapsed]);
- if (qgStatus.failedConditions.length === 0 && qgStatus.isCaycCompliant) {
+ /*
+ * Show if project has failed conditions or that
+ * it is a single non-cayc project
+ * In the context of an App, only show projects with failed conditions
+ */
+ if (
+ !(
+ qgStatus.failedConditions.length > 0 ||
+ (!qgStatus.isCaycCompliant && !isApplication(component.qualifier))
+ )
+ ) {
return null;
}
const showName = isApplication(component.qualifier);
const showSectionTitles =
+ isApplication(component.qualifier) ||
!qgStatus.isCaycCompliant ||
(overallFailedConditions.length > 0 && newCodeFailedConditions.length > 0);
{!collapsed && (
<>
- {!qgStatus.isCaycCompliant && (
+ {!qgStatus.isCaycCompliant && !isApplication(component.qualifier) && (
<div className="big-padded bordered-bottom overview-quality-gate-conditions-list">
- <CleanAsYouCodeWarning />
+ <CleanAsYouCodeWarning component={component} />
</div>
)}
import selectEvent from 'react-select-event';
import { getMeasuresWithPeriodAndMetrics } from '../../../../api/measures';
import { getProjectActivity } from '../../../../api/projectActivity';
-import { getQualityGateProjectStatus } from '../../../../api/quality-gates';
+import {
+ getApplicationQualityGate,
+ getQualityGateProjectStatus,
+} from '../../../../api/quality-gates';
import CurrentUserContextProvider from '../../../../app/components/current-user/CurrentUserContextProvider';
import { getActivityGraph, saveActivityGraph } from '../../../../components/activity-graph/utils';
import { isDiffMetric } from '../../../../helpers/measures';
import { mockMainBranch } from '../../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../../helpers/mocks/component';
import { mockAnalysis } from '../../../../helpers/mocks/project-activity';
-import { mockQualityGateProjectStatus } from '../../../../helpers/mocks/quality-gates';
+import {
+ mockQualityGateApplicationStatus,
+ mockQualityGateProjectStatus,
+} from '../../../../helpers/mocks/quality-gates';
import { mockLoggedInUser, mockPeriod } from '../../../../helpers/testMocks';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { ComponentQualifier } from '../../../../types/component';
+import { MetricKey } from '../../../../types/metrics';
import { GraphType } from '../../../../types/project-activity';
import { Measure, Metric } from '../../../../types/types';
import BranchOverview, { BRANCH_OVERVIEW_ACTIVITY_GRAPH, NO_CI_DETECTED } from '../BranchOverview';
expect(screen.getByText('Bar')).toBeInTheDocument();
});
+ 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: '4',
+ name: 'four',
+ conditions: [
+ {
+ comparator: 'GT',
+ metric: MetricKey.bugs,
+ status: 'ERROR',
+ value: '3',
+ errorThreshold: '0',
+ },
+ ],
+ isCaycCompliant: false,
+ status: 'ERROR',
+ },
+ ],
+ });
+ jest.mocked(getApplicationQualityGate).mockResolvedValueOnce(appStatus);
+
+ renderBranchOverview({ component });
+ expect(
+ await screen.findByText('overview.quality_gate.application.non_cayc.projects_x.3')
+ ).toBeInTheDocument();
+ expect(screen.getByText('first project')).toBeInTheDocument();
+ expect(screen.queryByText('second')).not.toBeInTheDocument();
+ expect(screen.getByText('number 3')).toBeInTheDocument();
+ });
+
it('should correctly show an app as empty', async () => {
jest.mocked(getMeasuresWithPeriodAndMetrics).mockResolvedValueOnce({
component: { key: '', name: '', qualifier: ComponentQualifier.Application, measures: [] },
<div
className="big-padded bordered-bottom overview-quality-gate-conditions-list"
>
- <CleanAsYouCodeWarning />
+ <CleanAsYouCodeWarning
+ component={
+ {
+ "breadcrumbs": [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "TRK",
+ "qualityGate": {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": [
+ {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": [],
+ }
+ }
+ />
</div>
<h4
className="big-padded overview-quality-gate-conditions-section-title"
</h3>
</div>
</ButtonPlain>
- <div
- className="big-padded bordered-bottom overview-quality-gate-conditions-list"
- >
- <CleanAsYouCodeWarning />
- </div>
<h4
className="big-padded overview-quality-gate-conditions-section-title"
>
overview.quality_gate.ignored_conditions.tooltip=At the start of a new code period, if very few lines have been added or modified, it might be difficult to reach the desired level of code coverage or duplications. To prevent Quality Gate failure when there's little that can be done about it, Quality Gate conditions about duplications in new code and coverage on new code are ignored until the number of new lines is at least 20. An administrator can disable this in the general settings.
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=This Quality Gate does not comply with Clean as You Code
-overview.quality_gate.conditions.cayc.details=Clean as You Code conditions ensure that only Clean Code passes the gate.
+overview.quality_gate.conditions.cayc.details.no_link=A Clean as You Code quality gate ensures that only Clean Code passes it.
+overview.quality_gate.conditions.cayc.details=A Clean as You Code quality gate ensures that only Clean Code passes it. {link} to view this project's quality gate.
+overview.quality_gate.conditions.cayc.details.link=Click here
overview.quality_gate.conditions.cayc.link=Learn more: 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.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