* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { isObject, some } from 'lodash';
import * as React from 'react';
module.exports = {
...jest.requireActual('react-intl'),
useIntl: () => ({
- formatMessage: ({ id }, values = {}) => [id, ...Object.values(values)].join('.'),
+ formatMessage: ({ id }, values = {}) => {
+ if (some(values, isObject)) {
+ return (
+ <>
+ {id}
+ {Object.entries(values).map(([key, value]) => (
+ <React.Fragment key={key}>{value}</React.Fragment>
+ ))}
+ </>
+ );
+ }
+ return [id, ...Object.values(values)].join('.');
+ },
}),
FormattedMessage: ({ id, values }: { id: string; values?: { [x: string]: React.ReactNode } }) => {
return (
expect(screen.getByText('content')).toHaveStyle({
'min-width': '1280px',
- 'max-width': '1400px',
+ 'max-width': '1280px',
});
});
});
};
export const LAYOUT_VIEWPORT_MIN_WIDTH = 1280;
-export const LAYOUT_VIEWPORT_MAX_WIDTH = 1400;
+export const LAYOUT_VIEWPORT_MAX_WIDTH = 1280;
export const LAYOUT_VIEWPORT_MAX_WIDTH_LARGE = 1680;
export const LAYOUT_MAIN_CONTENT_GUTTER = 60;
export const LAYOUT_SIDEBAR_WIDTH = 240;
--- /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 { HelperHintIcon, LightPrimary, QualityGateIndicator, TextMuted } from 'design-system';
+import React from 'react';
+import { useIntl } from 'react-intl';
+import HelpTooltip from '../../../components/controls/HelpTooltip';
+import { BranchLike } from '../../../types/branch-like';
+import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
+import { Component, Status } from '../../../types/types';
+import BranchQualityGateConditions from './BranchQualityGateConditions';
+
+interface Props {
+ status: Status;
+ branchLike?: BranchLike;
+ component: Pick<Component, 'key'>;
+ failedConditions: QualityGateStatusConditionEnhanced[];
+}
+
+export default function BranchQualityGate(props: Readonly<Props>) {
+ const { status, branchLike, component, failedConditions } = props;
+
+ return (
+ <>
+ <BranchQGStatus status={status} />
+ <BranchQualityGateConditions
+ branchLike={branchLike}
+ component={component}
+ failedConditions={failedConditions}
+ />
+ </>
+ );
+}
+
+function BranchQGStatus({ status }: Readonly<Pick<Props, 'status'>>) {
+ const intl = useIntl();
+
+ return (
+ <div className="sw-flex sw-items-center sw-mb-5">
+ <QualityGateIndicator
+ status={status}
+ className="sw-mr-2"
+ size="xl"
+ ariaLabel={intl.formatMessage(
+ { id: 'overview.quality_gate_x' },
+ { '0': intl.formatMessage({ id: `overview.gate.${status}` }) },
+ )}
+ />
+ <div className="sw-flex sw-flex-col sw-justify-around">
+ <div className="sw-flex sw-items-center">
+ <TextMuted
+ className="sw-body-sm"
+ text={intl.formatMessage({ id: 'overview.quality_gate' })}
+ />
+ <HelpTooltip
+ className="sw-ml-2"
+ overlay={intl.formatMessage({ id: 'overview.quality_gate.help' })}
+ >
+ <HelperHintIcon aria-label="help-tooltip" />
+ </HelpTooltip>
+ </div>
+ <div>
+ <LightPrimary as="h1" className="sw-heading-xl">
+ {intl.formatMessage({ id: `metric.level.${status}` })}
+ </LightPrimary>
+ </div>
+ </div>
+ </div>
+ );
+}
--- /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 { ChevronRightIcon, DangerButtonSecondary } from 'design-system';
+import React from 'react';
+import { useIntl } from 'react-intl';
+import { getBranchLikeQuery } from '../../../helpers/branch-like';
+import { getLocalizedMetricName } from '../../../helpers/l10n';
+import { formatMeasure, getShortType, isDiffMetric } from '../../../helpers/measures';
+import {
+ getComponentDrilldownUrl,
+ getComponentIssuesUrl,
+ getComponentSecurityHotspotsUrl,
+} from '../../../helpers/urls';
+import { BranchLike } from '../../../types/branch-like';
+import { IssueType } from '../../../types/issues';
+import { MetricKey, MetricType } from '../../../types/metrics';
+import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
+import { Component } from '../../../types/types';
+import {
+ METRICS_REPORTED_IN_OVERVIEW_CARDS,
+ RATING_METRICS_MAPPING,
+ RATING_TO_SEVERITIES_MAPPING,
+} from '../utils';
+
+interface Props {
+ branchLike?: BranchLike;
+ component: Pick<Component, 'key'>;
+ failedConditions: QualityGateStatusConditionEnhanced[];
+}
+
+export default function BranchQualityGateConditions(props: Readonly<Props>) {
+ const { branchLike, component, failedConditions } = props;
+
+ const filteredFailedConditions = failedConditions.filter(
+ (condition) => !METRICS_REPORTED_IN_OVERVIEW_CARDS.includes(condition.metric as MetricKey),
+ );
+
+ return (
+ <ul className="sw-flex sw-items-center sw-gap-2 sw-flex-wrap">
+ {filteredFailedConditions.map((condition) => (
+ <li key={condition.metric}>
+ <FailedQGCondition branchLike={branchLike} component={component} condition={condition} />
+ </li>
+ ))}
+ </ul>
+ );
+}
+
+function FailedQGCondition(
+ props: Readonly<
+ Pick<Props, 'branchLike' | 'component'> & { condition: QualityGateStatusConditionEnhanced }
+ >,
+) {
+ const { branchLike, component, condition } = props;
+ const url = getQGConditionUrl(component.key, condition, branchLike);
+
+ return (
+ <DangerButtonSecondary className="sw-px-2 sw-py-1 sw-rounded-1/2 sw-body-sm" to={url}>
+ <FailedMetric condition={condition} />
+ <ChevronRightIcon className="sw-ml-1" />
+ </DangerButtonSecondary>
+ );
+}
+
+interface FailedMetricProps {
+ condition: QualityGateStatusConditionEnhanced;
+}
+
+export function FailedMetric(props: Readonly<FailedMetricProps>) {
+ const {
+ condition: {
+ measure: { metric },
+ },
+ } = props;
+
+ if (metric.type === MetricType.Rating) {
+ return <FailedRatingMetric {...props} />;
+ }
+
+ return <FailedGeneralMetric {...props} />;
+}
+
+function FailedRatingMetric({ condition }: Readonly<FailedMetricProps>) {
+ const {
+ error,
+ measure: {
+ metric: { type, domain },
+ },
+ } = condition;
+ const intl = useIntl();
+
+ return (
+ <>
+ {intl.formatMessage(
+ { id: 'overview.failed_condition.x_required' },
+ {
+ metric: `${intl.formatMessage({
+ id: `metric_domain.${domain}`,
+ })} ${intl.formatMessage({ id: 'metric.type.RATING' }).toLowerCase()}`,
+ threshold: (
+ <strong className="sw-body-sm-highlight sw-ml-1">{formatMeasure(error, type)}</strong>
+ ),
+ },
+ )}
+ </>
+ );
+}
+
+function FailedGeneralMetric({ condition }: Readonly<FailedMetricProps>) {
+ const {
+ error,
+ measure: { metric },
+ } = condition;
+ const intl = useIntl();
+ const measureFormattingOptions = { decimals: 2, omitExtraDecimalZeros: true };
+
+ return (
+ <>
+ {intl.formatMessage(
+ { id: 'overview.failed_condition.x_required' },
+ {
+ metric: (
+ <>
+ <strong className="sw-body-sm-highlight sw-mr-1">
+ {formatMeasure(
+ condition.actual,
+ getShortType(metric.type),
+ measureFormattingOptions,
+ )}
+ </strong>
+ {getLocalizedMetricName(metric, true)}
+ </>
+ ),
+ threshold: (
+ <strong className="sw-body-sm-highlight sw-ml-1">
+ {condition.op === 'GT' ? <>≤</> : <>≥</>}{' '}
+ {formatMeasure(error, getShortType(metric.type), measureFormattingOptions)}
+ </strong>
+ ),
+ },
+ )}
+ </>
+ );
+}
+
+function getQGConditionUrl(
+ componentKey: string,
+ condition: QualityGateStatusConditionEnhanced,
+ branchLike?: BranchLike,
+) {
+ const { metric } = condition;
+ const sinceLeakPeriod = isDiffMetric(metric);
+ const issueType = RATING_METRICS_MAPPING[metric];
+
+ if (issueType) {
+ if (issueType === IssueType.SecurityHotspot) {
+ return getComponentSecurityHotspotsUrl(componentKey, {
+ ...getBranchLikeQuery(branchLike),
+ ...(sinceLeakPeriod ? { sinceLeakPeriod: 'true' } : {}),
+ });
+ }
+ return getComponentIssuesUrl(componentKey, {
+ resolved: 'false',
+ types: issueType,
+ ...getBranchLikeQuery(branchLike),
+ ...(sinceLeakPeriod ? { sinceLeakPeriod: 'true' } : {}),
+ ...(issueType !== IssueType.CodeSmell
+ ? { severities: RATING_TO_SEVERITIES_MAPPING[Number(condition.error) - 1] }
+ : {}),
+ });
+ }
+
+ return getComponentDrilldownUrl({
+ componentKey,
+ metric,
+ branchLike,
+ listView: true,
+ });
+}
import { MetricKey, MetricType } from '../../../types/metrics';
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
import { Component, Dict } from '../../../types/types';
+import { RATING_TO_SEVERITIES_MAPPING } from '../utils';
interface Props {
branchLike?: BranchLike;
}
getUrlForBugsOrVulnerabilities(type: string, inNewCodePeriod: boolean) {
- const RATING_TO_SEVERITIES_MAPPING = [
- 'BLOCKER,CRITICAL,MAJOR,MINOR',
- 'BLOCKER,CRITICAL,MAJOR',
- 'BLOCKER,CRITICAL',
- 'BLOCKER',
- ];
-
const { condition } = this.props;
const threshold = condition.level === 'ERROR' ? condition.error : condition.warning;
--- /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 { mockPullRequest } from '../../../../helpers/mocks/branch-like';
+import { mockComponent } from '../../../../helpers/mocks/component';
+import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates';
+import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { byLabelText, byRole } from '../../../../helpers/testSelector';
+import { MetricKey, MetricType } from '../../../../types/metrics';
+import { FCProps } from '../../../../types/misc';
+import { Status } from '../../utils';
+import BranchQualityGate from '../BranchQualityGate';
+
+it('renders failed QG', () => {
+ renderBranchQualityGate();
+
+ // Maintainability rating condition
+ expect(
+ byRole('link', {
+ name: 'overview.failed_condition.x_requiredmetric_domain.Maintainability metric.type.rating A',
+ }).get(),
+ ).toBeInTheDocument();
+
+ // Security Hotspots rating condition
+ expect(
+ byRole('link', {
+ name: 'overview.failed_condition.x_requiredmetric_domain.Security Review metric.type.rating A',
+ }).get(),
+ ).toBeInTheDocument();
+
+ // New code smells
+ expect(
+ byRole('link', {
+ name: 'overview.failed_condition.x_required 5 Code Smells ≤ 1',
+ }).get(),
+ ).toBeInTheDocument();
+
+ // Conditions to cover
+ expect(
+ byRole('link', {
+ name: 'overview.failed_condition.x_required 5 Conditions to cover ≥ 10',
+ }).get(),
+ ).toBeInTheDocument();
+
+ expect(byLabelText('overview.quality_gate_x.overview.gate.ERROR').get()).toBeInTheDocument();
+});
+
+it('renders passed QG', () => {
+ renderBranchQualityGate({ failedConditions: [], status: Status.OK });
+
+ expect(byLabelText('overview.quality_gate_x.overview.gate.OK').get()).toBeInTheDocument();
+ expect(byRole('link').query()).not.toBeInTheDocument();
+});
+
+function renderBranchQualityGate(props: Partial<FCProps<typeof BranchQualityGate>> = {}) {
+ return renderComponent(
+ <BranchQualityGate
+ status={Status.ERROR}
+ branchLike={mockPullRequest()}
+ component={mockComponent()}
+ failedConditions={[
+ mockQualityGateStatusConditionEnhanced({
+ actual: '5.0',
+ error: '1.0',
+ metric: MetricKey.new_maintainability_rating,
+ measure: mockMeasureEnhanced({
+ metric: mockMetric({
+ domain: 'Maintainability',
+ key: MetricKey.new_maintainability_rating,
+ name: 'Maintainability rating',
+ type: MetricType.Rating,
+ }),
+ }),
+ }),
+ mockQualityGateStatusConditionEnhanced({
+ actual: '5.0',
+ error: '1.0',
+ metric: MetricKey.new_security_review_rating,
+ measure: mockMeasureEnhanced({
+ metric: mockMetric({
+ domain: 'Security Review',
+ key: MetricKey.new_security_review_rating,
+ name: 'Security Review Rating',
+ type: MetricType.Rating,
+ }),
+ }),
+ }),
+ mockQualityGateStatusConditionEnhanced({
+ actual: '5',
+ error: '1',
+ metric: MetricKey.new_code_smells,
+ measure: mockMeasureEnhanced({
+ metric: mockMetric({
+ domain: 'Maintainability',
+ key: MetricKey.new_code_smells,
+ name: 'Code Smells',
+ type: MetricType.ShortInteger,
+ }),
+ }),
+ }),
+ mockQualityGateStatusConditionEnhanced({
+ actual: '5',
+ error: '10',
+ op: 'up',
+ metric: MetricKey.conditions_to_cover,
+ measure: mockMeasureEnhanced({
+ metric: mockMetric({
+ key: MetricKey.conditions_to_cover,
+ name: 'Conditions to cover',
+ type: MetricType.ShortInteger,
+ }),
+ }),
+ }),
+ ]}
+ {...props}
+ />,
+ );
+}
CenteredLayout,
CoverageIndicator,
DuplicationsIndicator,
- HelperHintIcon,
- Link,
Spinner,
- TextMuted,
} from 'design-system';
import { uniq } from 'lodash';
import * as React from 'react';
import { useEffect, useState } from 'react';
-import { FormattedMessage } from 'react-intl';
import { getMeasuresWithMetrics } from '../../../api/measures';
-import HelpTooltip from '../../../components/controls/HelpTooltip';
import { duplicationRatingConverter } from '../../../components/measure/utils';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
-import { translate } from '../../../helpers/l10n';
import { enhanceConditionWithMeasure, enhanceMeasuresWithMetrics } from '../../../helpers/measures';
import { isDefined } from '../../../helpers/types';
-import { getQualityGateUrl, getQualityGatesUrl } from '../../../helpers/urls';
import { useBranchStatusQuery } from '../../../queries/branch';
import { PullRequest } from '../../../types/branch-like';
import { IssueType } from '../../../types/issues';
import { Component, MeasureEnhanced } from '../../../types/types';
import MeasuresPanelIssueMeasure from '../branches/MeasuresPanelIssueMeasure';
import MeasuresPanelPercentMeasure from '../branches/MeasuresPanelPercentMeasure';
+import BranchQualityGate from '../components/BranchQualityGate';
import IgnoredConditionWarning from '../components/IgnoredConditionWarning';
import MetaTopBar from '../components/MetaTopBar';
-import QualityGateConditions from '../components/QualityGateConditions';
-import QualityGateStatusHeader from '../components/QualityGateStatusHeader';
-import QualityGateStatusPassedView from '../components/QualityGateStatusPassedView';
import SonarLintPromotion from '../components/SonarLintPromotion';
import '../styles.css';
import { MeasurementType, PR_METRICS, Status } from '../utils';
return null;
}
- const path =
- component.qualityGate === undefined
- ? getQualityGatesUrl()
- : getQualityGateUrl(component.qualityGate.name);
-
const failedConditions = conditions
.filter((condition) => condition.level === 'ERROR')
.map((c) => enhanceConditionWithMeasure(c, measures))
return (
<CenteredLayout>
- <div className="it__pr-overview sw-mt-12">
- <MetaTopBar branchLike={branchLike} measures={measures} />
- <BasicSeparator className="sw-my-4" />
-
- {ignoredConditions && <IgnoredConditionWarning />}
-
- <div className="sw-flex sw-flex-col sw-mr-12 width-30">
- <Card>
- {status && (
- <QualityGateStatusHeader
- status={status}
- failedConditionCount={failedConditions.length}
- />
- )}
-
- <div className="sw-flex sw-items-center sw-mb-4">
- <TextMuted text={translate('overview.on_new_code_long')} />
- <HelpTooltip
- className="sw-ml-2"
- overlay={
- <FormattedMessage
- defaultMessage={translate('overview.quality_gate.conditions_on_new_code')}
- id="overview.quality_gate.conditions_on_new_code"
- values={{
- link: <Link to={path}>{translate('overview.quality_gate')}</Link>,
- }}
- />
- }
- >
- <HelperHintIcon aria-label="help-tooltip" />
- </HelpTooltip>
- </div>
-
- {status === Status.OK && failedConditions.length === 0 && (
- <QualityGateStatusPassedView />
- )}
-
- {status !== Status.OK && <BasicSeparator />}
-
- {failedConditions.length > 0 && (
- <div>
- <QualityGateConditions
- branchLike={branchLike}
- collapsible
- component={component}
- failedConditions={failedConditions}
- />
- </div>
- )}
- </Card>
- <SonarLintPromotion qgConditions={conditions} />
- </div>
-
- <div className="sw-flex-1">
- <div className="sw-grid sw-grid-cols-2 sw-gap-4 sw-mt-4">
- {[
- IssueType.Bug,
- IssueType.CodeSmell,
- IssueType.Vulnerability,
- IssueType.SecurityHotspot,
- ].map((type: IssueType) => (
- <Card key={type} className="sw-p-8">
- <MeasuresPanelIssueMeasure
- branchLike={branchLike}
- component={component}
- isNewCodeTab
- measures={measures}
- type={type}
- />
- </Card>
- ))}
-
- {[MeasurementType.Coverage, MeasurementType.Duplication].map(
- (type: MeasurementType) => (
+ <div className="it__pr-overview sw-mt-12 sw-grid sw-grid-cols-12">
+ <div className="sw-col-start-2 sw-col-span-10">
+ <MetaTopBar branchLike={branchLike} measures={measures} />
+ <BasicSeparator className="sw-my-4" />
+
+ {ignoredConditions && <IgnoredConditionWarning />}
+
+ {status && (
+ <BranchQualityGate
+ branchLike={branchLike}
+ component={component}
+ status={status}
+ failedConditions={failedConditions}
+ />
+ )}
+
+ <div className="sw-flex-1">
+ <div className="sw-grid sw-grid-cols-2 sw-gap-4 sw-mt-4">
+ {[
+ IssueType.Bug,
+ IssueType.CodeSmell,
+ IssueType.Vulnerability,
+ IssueType.SecurityHotspot,
+ ].map((type: IssueType) => (
<Card key={type} className="sw-p-8">
- <MeasuresPanelPercentMeasure
+ <MeasuresPanelIssueMeasure
branchLike={branchLike}
component={component}
+ isNewCodeTab
measures={measures}
- ratingIcon={renderMeasureIcon(type)}
type={type}
- useDiffMetric
/>
</Card>
- ),
- )}
+ ))}
+
+ {[MeasurementType.Coverage, MeasurementType.Duplication].map(
+ (type: MeasurementType) => (
+ <Card key={type} className="sw-p-8">
+ <MeasuresPanelPercentMeasure
+ branchLike={branchLike}
+ component={component}
+ measures={measures}
+ ratingIcon={renderMeasureIcon(type)}
+ type={type}
+ useDiffMetric
+ />
+ </Card>
+ ),
+ )}
+ </div>
</div>
+ <SonarLintPromotion qgConditions={conditions} />
</div>
</div>
</CenteredLayout>
import { mockQualityGateProjectCondition } from '../../../../helpers/mocks/quality-gates';
import { mockLoggedInUser, mockMeasure, mockMetric } from '../../../../helpers/testMocks';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { byLabelText, byRole } from '../../../../helpers/testSelector';
import { ComponentPropsType } from '../../../../helpers/testUtils';
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey, MetricType } from '../../../../types/metrics';
renderPullRequestOverview();
await waitFor(async () => expect(await screen.findByText('metric.level.OK')).toBeInTheDocument());
+ expect(screen.getByLabelText('overview.quality_gate_x.overview.gate.OK')).toBeInTheDocument();
+
expect(screen.getByText('metric.new_lines.name')).toBeInTheDocument();
expect(screen.getByText(/overview.last_analysis_x/)).toBeInTheDocument();
});
renderPullRequestOverview();
await waitFor(async () =>
- expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument(),
+ expect(
+ await byLabelText('overview.quality_gate_x.overview.gate.ERROR').find(),
+ ).toBeInTheDocument(),
);
- expect(await screen.findByText('1.0% metric.new_coverage.name')).toBeInTheDocument();
- expect(await screen.findByText('quality_gates.operator.GT 2.0%')).toBeInTheDocument();
-
expect(
- await screen.findByText('1.0% metric.duplicated_lines.name quality_gates.conditions.new_code'),
+ byRole('link', {
+ name: 'overview.failed_condition.x_required 10.0% duplicated_lines ≤ 1.0%',
+ }).get(),
+ ).toBeInTheDocument();
+ expect(
+ byRole('link', {
+ name: 'overview.failed_condition.x_required 10 new_bugs ≤ 3',
+ }).get(),
).toBeInTheDocument();
- expect(await screen.findByText('quality_gates.operator.GT 1.0%')).toBeInTheDocument();
-
- expect(screen.getByText('quality_gates.operator.GT 3')).toBeInTheDocument();
});
function renderPullRequestOverview(
import { IssueType } from '../../types/issues';
import { MetricKey } from '../../types/metrics';
import { AnalysisMeasuresVariations, MeasureHistory } from '../../types/project-activity';
-import { RawQuery } from '../../types/types';
+import { Dict, RawQuery } from '../../types/types';
export const METRICS: string[] = [
// quality gate
},
};
+export const RATING_TO_SEVERITIES_MAPPING = [
+ 'BLOCKER,CRITICAL,MAJOR,MINOR',
+ 'BLOCKER,CRITICAL,MAJOR',
+ 'BLOCKER,CRITICAL',
+ 'BLOCKER',
+];
+
+export const RATING_METRICS_MAPPING: Dict<IssueType> = {
+ [MetricKey.reliability_rating]: IssueType.Bug,
+ [MetricKey.new_reliability_rating]: IssueType.Bug,
+ [MetricKey.security_rating]: IssueType.Vulnerability,
+ [MetricKey.new_security_rating]: IssueType.Vulnerability,
+ [MetricKey.sqale_rating]: IssueType.CodeSmell,
+ [MetricKey.new_maintainability_rating]: IssueType.CodeSmell,
+ [MetricKey.security_review_rating]: IssueType.SecurityHotspot,
+ [MetricKey.new_security_review_rating]: IssueType.SecurityHotspot,
+};
+
+export const METRICS_REPORTED_IN_OVERVIEW_CARDS = [
+ MetricKey.new_violations,
+ MetricKey.violations,
+ MetricKey.new_coverage,
+ MetricKey.coverage,
+ MetricKey.new_security_hotspots_reviewed,
+ MetricKey.security_hotspots_reviewed,
+ MetricKey.new_duplicated_lines_density,
+ MetricKey.duplicated_lines_density,
+];
+
export function getIssueRatingName(type: IssueType) {
return translate('metric_domain', ISSUETYPE_METRIC_KEYS_MAP[type].ratingName);
}
import { EditionKey } from '../types/editions';
import { IssueScope, IssueSeverity, IssueStatus, IssueType, RawIssue } from '../types/issues';
import { Language } from '../types/languages';
+import { MetricKey, MetricType } from '../types/metrics';
import { Notification } from '../types/notifications';
import { DumpStatus, DumpTask } from '../types/project-dump';
import { TaskStatuses } from '../types/tasks';
};
}
-export function mockMetric(overrides: Partial<Pick<Metric, 'key' | 'name' | 'type'>> = {}): Metric {
- const key = overrides.key || 'coverage';
+export function mockMetric(
+ overrides: Partial<Pick<Metric, 'key' | 'name' | 'type' | 'domain'>> = {},
+): Metric {
+ const key = overrides.key || MetricKey.coverage;
const name = overrides.name || key;
- const type = overrides.type || 'PERCENT';
+ const type = overrides.type || MetricType.Percent;
return {
+ ...overrides,
id: key,
key,
name,
metric.ncloc_language_distribution.name=Lines of Code Per Language
metric.new_blocker_violations.description=New Blocker issues
metric.new_blocker_violations.name=New Blocker Issues
+metric.new_blocker_violations.short_name=Blocker Issues
metric.new_branch_coverage.description=Condition coverage of new/changed code
metric.new_branch_coverage.name=Condition Coverage on New Code
metric.new_branch_coverage.extra_short_name=Condition Coverage
metric.new_coverage.short_name=Coverage
metric.new_critical_violations.description=New Critical issues
metric.new_critical_violations.name=New Critical Issues
+metric.new_critical_violations.short_name=Critical Issues
metric.new_development_cost.description=Development cost on new code
metric.new_development_cost.name=Development Cost on New Code
metric.new_duplicated_blocks.name=Duplicated Blocks on New Code
metric.new_duplicated_lines_density.extra_short_name=Density
metric.new_info_violations.description=New Info issues
metric.new_info_violations.name=New Info Issues
+metric.new_info_violations.short_name=Info Issues
metric.new_it_branch_coverage.description=Integration tests condition coverage of new/changed code
metric.new_it_branch_coverage.name=Condition Coverage by IT on New Code
metric.new_it_conditions_to_cover.description=New conditions to cover by integration tests
metric.new_maintainability_rating.extra_short_name=Rating
metric.new_major_violations.description=New Major issues
metric.new_major_violations.name=New Major Issues
+metric.new_major_violations.short_name=Major Issues
metric.new_minor_violations.description=New Minor issues
metric.new_minor_violations.name=New Minor Issues
+metric.new_minor_violations.short_name=Minor Issues
metric.new_lines.name=New Lines
metric.new_lines.description=New lines
metric.new_lines.short_name=Lines
#------------------------------------------------------------------------------
overview.1_condition_failed=1 failed condition
overview.X_conditions_failed={0} failed conditions
+overview.failed_condition.x_required={metric} required {threshold}
overview.fix_failed_conditions_with_sonarlint=Fix issues before they fail your Quality Gate with {link} in your IDE. Power up with connected mode!
overview.quality_gate.status=Quality Gate Status
overview.quality_gate=Quality Gate