overviewCardSuccessIcon: COLORS.green[200],
// overview software impact breakdown
- overviewSoftwareImpactSeverityNeutral: [247, 249, 252],
+ overviewSoftwareImpactSeverityNeutral: COLORS.blueGrey[35],
overviewSoftwareImpactSeverityHigh: COLORS.red[100],
overviewSoftwareImpactSeverityMedium: COLORS.yellow[100],
overviewSoftwareImpactSeverityLow: COLORS.blue[100],
import { QualityGateStatus } from '../../../types/quality-gates';
import { Component, MeasureEnhanced, Metric, Period, QualityGate } from '../../../types/types';
import { AnalysisStatus } from '../components/AnalysisStatus';
-import SonarLintPromotion from '../components/SonarLintPromotion';
import { MeasuresTabs } from '../utils';
import ActivityPanel from './ActivityPanel';
import BranchMetaTopBar from './BranchMetaTopBar';
import NoCodeWarning from './NoCodeWarning';
import OverallCodeMeasuresPanel from './OverallCodeMeasuresPanel';
import QualityGatePanel from './QualityGatePanel';
+import SonarLintPromotion from './SonarLintPromotion';
import { TabsPanel } from './TabsPanel';
export interface BranchOverviewRendererProps {
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { getLeakValue } from '../../../components/measure/utils';
-import DrilldownLink from '../../../components/shared/DrilldownLink';
-import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n';
-import { findMeasure, formatMeasure, localizeMetric } from '../../../helpers/measures';
-import { BranchLike } from '../../../types/branch-like';
-import { MetricKey } from '../../../types/metrics';
-import { Component, MeasureEnhanced } from '../../../types/types';
-
-export interface DebtValueProps {
- branchLike?: BranchLike;
- component: Component;
- measures: MeasureEnhanced[];
- useDiffMetric?: boolean;
-}
-
-export function DebtValue(props: DebtValueProps) {
- const { branchLike, component, measures, useDiffMetric = false } = props;
- const metricKey = useDiffMetric ? MetricKey.new_technical_debt : MetricKey.sqale_index;
- const measure = findMeasure(measures, metricKey);
-
- let value;
- let metricName;
- if (measure) {
- value = useDiffMetric ? getLeakValue(measure) : measure.value;
- metricName = getLocalizedMetricName(measure.metric, true);
- } else {
- metricName = localizeMetric(metricKey);
- }
- const formattedValue = formatMeasure(value, 'WORK_DUR');
-
- return (
- <>
- {value === undefined ? (
- <span aria-label={translate('no_data')} className="overview-measures-empty-value" />
- ) : (
- <DrilldownLink
- ariaLabel={translateWithParameters(
- 'overview.see_more_details_on_x_of_y',
- formattedValue,
- metricName,
- )}
- branchLike={branchLike}
- className="overview-measures-value text-light"
- component={component.key}
- metric={metricKey}
- >
- {formattedValue}
- </DrilldownLink>
- )}
- <span className="big-spacer-left">{metricName}</span>
- </>
- );
-}
-
-export default React.memo(DebtValue);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { DrilldownLink } from 'design-system';
-import * as React from 'react';
-import { translateWithParameters } from '../../../helpers/l10n';
-import { findMeasure, formatMeasure, localizeMetric } from '../../../helpers/measures';
-import { getComponentDrilldownUrl } from '../../../helpers/urls';
-import { BranchLike } from '../../../types/branch-like';
-import { MetricKey, MetricType } from '../../../types/metrics';
-import { Component, MeasureEnhanced } from '../../../types/types';
-
-export interface DrilldownMeasureValueProps {
- branchLike?: BranchLike;
- component: Component;
- measures: MeasureEnhanced[];
- metric: MetricKey;
-}
-
-export function DrilldownMeasureValue(props: DrilldownMeasureValueProps) {
- const { branchLike, component, measures, metric } = props;
- const measure = findMeasure(measures, metric);
-
- if (!measure || measure.value === undefined) {
- return <span>–</span>;
- }
-
- const url = getComponentDrilldownUrl({
- branchLike,
- componentKey: component.key,
- metric,
- });
-
- return (
- <span>
- <DrilldownLink
- aria-label={translateWithParameters(
- 'overview.see_more_details_on_x_y',
- measure.value,
- localizeMetric(metric),
- )}
- to={url}
- >
- {formatMeasure(measure.value, MetricType.ShortInteger)}
- </DrilldownLink>
- </span>
- );
-}
-
-export default React.memo(DrilldownMeasureValue);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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';
-
-interface Props {
- category: React.ReactElement;
- rating: React.ReactElement | null;
-}
-
-export default function MeasuresPanelCard(
- props: React.PropsWithChildren<Props & React.HTMLAttributes<HTMLDivElement>>,
-) {
- const { category, children, rating, ...attributes } = props;
-
- return (
- <div className="sw-flex sw-justify-between sw-items-center" {...attributes}>
- <div className="sw-flex sw-flex-col sw-justify-between">
- <div className="sw-body-sm-highlight sw-flex sw-items-center">{category}</div>
-
- <div className="sw-mt-3">{children}</div>
- </div>
-
- <div>{rating}</div>
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { LinkBox, TextMuted } from 'design-system';
+import * as React from 'react';
+import { Path } from 'react-router-dom';
+import IssueTypeIcon from '../../../components/icons/IssueTypeIcon';
+import MeasureIndicator from '../../../components/measure/MeasureIndicator';
+import {
+ DEFAULT_ISSUES_QUERY,
+ isIssueMeasure,
+ propsToIssueParams,
+} from '../../../components/shared/utils';
+import { getBranchLikeQuery } from '../../../helpers/branch-like';
+import { translate } from '../../../helpers/l10n';
+import { formatMeasure, isDiffMetric, localizeMetric } from '../../../helpers/measures';
+import { getOperatorLabel } from '../../../helpers/qualityGates';
+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, Dict } from '../../../types/types';
+import { RATING_TO_SEVERITIES_MAPPING } from '../utils';
+
+interface Props {
+ branchLike?: BranchLike;
+ component: Pick<Component, 'key'>;
+ condition: QualityGateStatusConditionEnhanced;
+}
+
+export default class QualityGateCondition extends React.PureComponent<Props> {
+ getIssuesUrl = (inNewCodePeriod: boolean, customQuery: Dict<string>) => {
+ const query: Dict<string | undefined> = {
+ ...DEFAULT_ISSUES_QUERY,
+ ...getBranchLikeQuery(this.props.branchLike),
+ ...customQuery,
+ };
+ if (inNewCodePeriod) {
+ Object.assign(query, { inNewCodePeriod: 'true' });
+ }
+ return getComponentIssuesUrl(this.props.component.key, query);
+ };
+
+ getUrlForSecurityHotspot(inNewCodePeriod: boolean) {
+ const query: Dict<string | undefined> = {
+ ...getBranchLikeQuery(this.props.branchLike),
+ };
+ if (inNewCodePeriod) {
+ Object.assign(query, { inNewCodePeriod: 'true' });
+ }
+ return getComponentSecurityHotspotsUrl(this.props.component.key, query);
+ }
+
+ getUrlForCodeSmells(inNewCodePeriod: boolean) {
+ return this.getIssuesUrl(inNewCodePeriod, { types: 'CODE_SMELL' });
+ }
+
+ getUrlForBugsOrVulnerabilities(type: string, inNewCodePeriod: boolean) {
+ const { condition } = this.props;
+ const threshold = condition.level === 'ERROR' ? condition.error : condition.warning;
+
+ return this.getIssuesUrl(inNewCodePeriod, {
+ types: type,
+ severities: RATING_TO_SEVERITIES_MAPPING[Number(threshold) - 1],
+ });
+ }
+
+ wrapWithLink(children: React.ReactNode) {
+ const { branchLike, component, condition } = this.props;
+
+ const metricKey = condition.measure.metric.key;
+
+ const METRICS_TO_URL_MAPPING: Dict<() => Path> = {
+ [MetricKey.reliability_rating]: () =>
+ this.getUrlForBugsOrVulnerabilities(IssueType.Bug, false),
+ [MetricKey.new_reliability_rating]: () =>
+ this.getUrlForBugsOrVulnerabilities(IssueType.Bug, true),
+ [MetricKey.security_rating]: () =>
+ this.getUrlForBugsOrVulnerabilities(IssueType.Vulnerability, false),
+ [MetricKey.new_security_rating]: () =>
+ this.getUrlForBugsOrVulnerabilities(IssueType.Vulnerability, true),
+ [MetricKey.sqale_rating]: () => this.getUrlForCodeSmells(false),
+ [MetricKey.new_maintainability_rating]: () => this.getUrlForCodeSmells(true),
+ [MetricKey.security_hotspots_reviewed]: () => this.getUrlForSecurityHotspot(false),
+ [MetricKey.new_security_hotspots_reviewed]: () => this.getUrlForSecurityHotspot(true),
+ };
+
+ if (METRICS_TO_URL_MAPPING[metricKey]) {
+ return <LinkBox to={METRICS_TO_URL_MAPPING[metricKey]()}>{children}</LinkBox>;
+ }
+
+ const url = isIssueMeasure(condition.measure.metric.key)
+ ? getComponentIssuesUrl(component.key, {
+ ...propsToIssueParams(condition.measure.metric.key, condition.period != null),
+ ...getBranchLikeQuery(branchLike),
+ })
+ : getComponentDrilldownUrl({
+ componentKey: component.key,
+ metric: condition.measure.metric.key,
+ branchLike,
+ listView: true,
+ });
+
+ return <LinkBox to={url}>{children}</LinkBox>;
+ }
+
+ getPrimaryText = () => {
+ const { condition } = this.props;
+ const { measure } = condition;
+ const { metric } = measure;
+ const isDiff = isDiffMetric(metric.key);
+
+ const subText =
+ !isDiff && condition.period != null
+ ? `${localizeMetric(metric.key)} ${translate('quality_gates.conditions.new_code')}`
+ : localizeMetric(metric.key);
+
+ if (metric.type !== MetricType.Rating) {
+ const actual = (condition.period ? measure.period?.value : measure.value) as string;
+ const formattedValue = formatMeasure(actual, metric.type, {
+ decimal: 2,
+ omitExtraDecimalZeros: metric.type === MetricType.Percent,
+ });
+ return `${formattedValue} ${subText}`;
+ }
+
+ return subText;
+ };
+
+ render() {
+ const { condition } = this.props;
+ const { measure } = condition;
+ const { metric } = measure;
+
+ const threshold = (condition.level === 'ERROR' ? condition.error : condition.warning) as string;
+ const actual = (condition.period ? measure.period?.value : measure.value) as string;
+
+ const operator = getOperatorLabel(condition.op, metric);
+
+ return this.wrapWithLink(
+ <div className="sw-flex sw-items-center sw-p-2">
+ <MeasureIndicator
+ className="sw-flex sw-justify-center sw-w-6 sw-mx-4"
+ decimals={2}
+ metricKey={measure.metric.key}
+ metricType={measure.metric.type}
+ value={actual}
+ />
+ <div className="sw-flex sw-flex-col sw-text-sm">
+ <div className="sw-flex sw-items-center">
+ <IssueTypeIcon className="sw-mr-2" query={metric.key} />
+ <span className="sw-body-sm-highlight sw-text-ellipsis sw-max-w-abs-300">
+ {this.getPrimaryText()}
+ </span>
+ </div>
+ <TextMuted text={`${operator} ${formatMeasure(threshold, metric.type)}`} />
+ </div>
+ </div>,
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { BasicSeparator, Link } from 'design-system';
+import { sortBy } from 'lodash';
+import * as React from 'react';
+import { translate } from '../../../helpers/l10n';
+import { BranchLike } from '../../../types/branch-like';
+import { MetricKey } from '../../../types/metrics';
+import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
+import { Component } from '../../../types/types';
+import QualityGateCondition from './QualityGateCondition';
+import QualityGateSimplifiedCondition from './QualityGateSimplifiedCondition';
+
+const LEVEL_ORDER = ['ERROR', 'WARN'];
+
+export interface QualityGateConditionsProps {
+ branchLike?: BranchLike;
+ component: Pick<Component, 'key'>;
+ collapsible?: boolean;
+ failedConditions: QualityGateStatusConditionEnhanced[];
+ isBuiltInQualityGate?: boolean;
+}
+
+const MAX_CONDITIONS = 5;
+
+export function QualityGateConditions(props: Readonly<QualityGateConditionsProps>) {
+ const { branchLike, collapsible, component, failedConditions, isBuiltInQualityGate } = props;
+ const [collapsed, toggleCollapsed] = React.useState(Boolean(collapsible));
+
+ const handleToggleCollapsed = React.useCallback(() => toggleCollapsed(!collapsed), [collapsed]);
+
+ const isSimplifiedCondition = React.useCallback(
+ (condition: QualityGateStatusConditionEnhanced) => {
+ const { metric } = condition.measure;
+ return metric.key === MetricKey.new_violations && isBuiltInQualityGate;
+ },
+ [isBuiltInQualityGate],
+ );
+
+ const sortedConditions = sortBy(failedConditions, (condition) =>
+ LEVEL_ORDER.indexOf(condition.level),
+ );
+
+ let renderConditions;
+ let renderCollapsed;
+
+ if (collapsed && sortedConditions.length > MAX_CONDITIONS) {
+ renderConditions = sortedConditions.slice(0, MAX_CONDITIONS);
+ renderCollapsed = true;
+ } else {
+ renderConditions = sortedConditions;
+ renderCollapsed = false;
+ }
+
+ return (
+ <ul id="overview-quality-gate-conditions-list" className="sw-mb-2">
+ {renderConditions.map((condition) => (
+ <div key={condition.measure.metric.key}>
+ {isSimplifiedCondition(condition) ? (
+ <QualityGateSimplifiedCondition
+ branchLike={branchLike}
+ component={component}
+ condition={condition}
+ />
+ ) : (
+ <QualityGateCondition
+ branchLike={branchLike}
+ component={component}
+ condition={condition}
+ />
+ )}
+ <BasicSeparator />
+ </div>
+ ))}
+ {renderCollapsed && (
+ <li className="sw-flex sw-justify-center sw-my-3">
+ <Link onClick={handleToggleCollapsed} to={{}} preventDefault>
+ <span className="sw-font-semibold sw-text-sm">{translate('show_more')}</span>
+ </Link>
+ </li>
+ )}
+ </ul>
+ );
+}
+
+export default React.memo(QualityGateConditions);
import { QualityGateStatus } from '../../../types/quality-gates';
import { CaycStatus, Component, QualityGate } from '../../../types/types';
import IgnoredConditionWarning from '../components/IgnoredConditionWarning';
-import QualityGateStatusHeader from '../components/QualityGateStatusHeader';
-import QualityGateStatusPassedView from '../components/QualityGateStatusPassedView';
-import { QualityGateStatusTitle } from '../components/QualityGateStatusTitle';
import ApplicationNonCaycProjectWarning from './ApplicationNonCaycProjectWarning';
import CleanAsYouCodeWarning from './CleanAsYouCodeWarning';
import QualityGatePanelSection from './QualityGatePanelSection';
+import QualityGateStatusHeader from './QualityGateStatusHeader';
+import QualityGateStatusPassedView from './QualityGateStatusPassedView';
+import { QualityGateStatusTitle } from './QualityGateStatusTitle';
export interface QualityGatePanelProps {
component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>;
QualityGateStatusConditionEnhanced,
} from '../../../types/quality-gates';
import { QualityGate } from '../../../types/types';
-import QualityGateConditions from '../components/QualityGateConditions';
import ZeroNewIssuesSimplificationGuide from '../components/ZeroNewIssuesSimplificationGuide';
+import QualityGateConditions from './QualityGateConditions';
export interface QualityGatePanelSectionProps {
branchLike?: BranchLike;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { Highlight, LinkBox } from 'design-system';
+import * as React from 'react';
+import { propsToIssueParams } from '../../../components/shared/utils';
+import { getBranchLikeQuery } from '../../../helpers/branch-like';
+import { translate } from '../../../helpers/l10n';
+import { formatMeasure, isDiffMetric, localizeMetric } from '../../../helpers/measures';
+import { getComponentIssuesUrl } from '../../../helpers/urls';
+import { BranchLike } from '../../../types/branch-like';
+import { MetricKey, MetricType } from '../../../types/metrics';
+import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
+import { Component } from '../../../types/types';
+
+interface Props {
+ branchLike?: BranchLike;
+ component: Pick<Component, 'key'>;
+ condition: QualityGateStatusConditionEnhanced;
+}
+
+export default function QualityGateSimplifiedCondition({
+ branchLike,
+ component,
+ condition,
+}: Readonly<Props>) {
+ const getPrimaryText = () => {
+ const { measure } = condition;
+ const { metric } = measure;
+ const isDiff = isDiffMetric(metric.key);
+
+ const subText =
+ !isDiff && condition.period != null
+ ? `${localizeMetric(metric.key)} ${translate('quality_gates.conditions.new_code')}`
+ : localizeMetric(metric.key);
+
+ return subText;
+ };
+
+ const { measure } = condition;
+ const { metric } = measure;
+
+ const value = (condition.period ? measure.period?.value : measure.value) as string;
+
+ const formattedValue = formatMeasure(value, MetricType.ShortInteger, {
+ decimals: 0,
+ omitExtraDecimalZeros: metric.type === MetricType.Percent,
+ });
+
+ return (
+ <LinkBox
+ to={getComponentIssuesUrl(component.key, {
+ ...propsToIssueParams(condition.measure.metric.key, condition.period != null),
+ ...getBranchLikeQuery(branchLike),
+ })}
+ >
+ <div className="sw-flex sw-p-2 sw-items-baseline">
+ <Highlight className="sw-mx-4 sw-w-6 sw-my-0 sw-text-right">{formattedValue}</Highlight>
+ <Highlight
+ className="sw-text-ellipsis sw-pr-4"
+ data-guiding-id={
+ metric.key === MetricKey.new_violations
+ ? 'overviewZeroNewIssuesSimplification'
+ : undefined
+ }
+ >
+ {getPrimaryText()}
+ </Highlight>
+ </div>
+ </LinkBox>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { QualityGateIndicator, TextError } from 'design-system';
+import React from 'react';
+import { useIntl } from 'react-intl';
+import { translate } from '../../../helpers/l10n';
+import { Status } from '../../../types/types';
+
+interface Props {
+ status: Status;
+ failedConditionCount: number;
+}
+
+export default function QualityGateStatusHeader(props: Props) {
+ const { status, failedConditionCount } = props;
+ const intl = useIntl();
+
+ return (
+ <div className="sw-flex sw-items-center sw-mb-4">
+ <QualityGateIndicator status={status} className="sw-mr-2" size="xl" />
+ <div className="sw-flex sw-flex-col">
+ <span className="sw-heading-lg">{translate('metric.level', status)}</span>
+ {failedConditionCount > 0 && (
+ <TextError
+ className="sw-font-regular"
+ text={intl.formatMessage(
+ { id: 'overview.X_conditions_failed' },
+ {
+ conditions: <strong>{failedConditionCount}</strong>,
+ },
+ )}
+ />
+ )}
+ </div>
+ </div>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { OverviewQGPassedIcon } from 'design-system';
+import React from 'react';
+import { translate } from '../../../helpers/l10n';
+
+export default function QualityGateStatusPassedView() {
+ return (
+ <div className="sw-flex sw-items-center sw-justify-center sw-flex-col">
+ <OverviewQGPassedIcon className="sw-my-12" />
+ <p className="sw-mb-8">{translate('overview.passed.clean_code')}</p>
+ </div>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { BasicSeparator, HelperHintIcon, PageTitle } from 'design-system';
+import React from 'react';
+import HelpTooltip from '../../../components/controls/HelpTooltip';
+import { translate } from '../../../helpers/l10n';
+
+export function QualityGateStatusTitle() {
+ return (
+ <>
+ <div className="sw-flex sw-items-center sw-mb-4 sw--mt-2">
+ <div className="sw-flex sw-items-center">
+ <PageTitle as="h2" text={translate('overview.quality_gate.status')} />
+ <HelpTooltip
+ className="sw-ml-2"
+ overlay={<div className="sw-my-4">{translate('overview.quality_gate.help')}</div>}
+ >
+ <HelperHintIcon aria-label="help-tooltip" />
+ </HelpTooltip>
+ </div>
+ </div>
+ <BasicSeparator className="sw--mx-6" />
+ </>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { Card, DiscreetLink } from 'design-system';
+import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
+import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
+import SonarLintIcon from '../../../components/icons/SonarLintIcon';
+import { translate } from '../../../helpers/l10n';
+import { MetricKey } from '../../../types/metrics';
+import { QualityGateStatusCondition } from '../../../types/quality-gates';
+import { CurrentUser } from '../../../types/users';
+
+export interface SonarLintPromotionProps {
+ currentUser: CurrentUser;
+ qgConditions?: QualityGateStatusCondition[];
+}
+
+const CONDITIONS_TO_SHOW = [
+ MetricKey.new_blocker_violations,
+ MetricKey.new_critical_violations,
+ MetricKey.new_info_violations,
+ MetricKey.new_violations,
+ MetricKey.new_major_violations,
+ MetricKey.new_minor_violations,
+ MetricKey.new_code_smells,
+ MetricKey.new_bugs,
+ MetricKey.new_vulnerabilities,
+ MetricKey.new_security_rating,
+ MetricKey.new_maintainability_rating,
+ MetricKey.new_reliability_rating,
+];
+
+export function SonarLintPromotion({ currentUser, qgConditions }: SonarLintPromotionProps) {
+ const showMessage = qgConditions?.some(
+ (qgCondition) =>
+ CONDITIONS_TO_SHOW.includes(qgCondition.metric) && qgCondition.level === 'ERROR',
+ );
+ if (!showMessage || currentUser.usingSonarLintConnectedMode) {
+ return null;
+ }
+ return (
+ <Card className="it__overview__sonarlint-promotion sw-my-4 sw-body-sm">
+ <FormattedMessage
+ id="overview.fix_failed_conditions_with_sonarlint"
+ defaultMessage={translate('overview.fix_failed_conditions_with_sonarlint')}
+ values={{
+ link: (
+ <>
+ <DiscreetLink
+ to="https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=sonarqube"
+ rel="noopener noreferrer"
+ target="_blank"
+ showExternalIcon={false}
+ className="sw-mr-1"
+ >
+ SonarLint
+ </DiscreetLink>
+ <span className="sw-align-middle">
+ <SonarLintIcon size={16} />
+ </span>
+ </>
+ ),
+ }}
+ />
+ </Card>
+ );
+}
+
+export default withCurrentUserContext(SonarLintPromotion);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { screen } from '@testing-library/react';
-import * as React from 'react';
-import { mockMainBranch } from '../../../../helpers/mocks/branch-like';
-import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks';
-import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-import { MetricKey } from '../../../../types/metrics';
-import { DebtValue, DebtValueProps } from '../DebtValue';
-
-it('should render correctly', () => {
- renderDebtValue();
-
- expect(
- screen.getByLabelText(
- 'overview.see_more_details_on_x_of_y.work_duration.x_minutes.1.sqale_index',
- ),
- ).toBeInTheDocument();
-
- expect(screen.getByText('sqale_index')).toBeInTheDocument();
-});
-
-it('should render diff metric correctly', () => {
- renderDebtValue({ useDiffMetric: true });
-
- expect(
- screen.getByLabelText(
- 'overview.see_more_details_on_x_of_y.work_duration.x_minutes.1.new_technical_debt',
- ),
- ).toBeInTheDocument();
-
- expect(screen.getByText('new_technical_debt')).toBeInTheDocument();
-});
-
-it('should handle missing measure', () => {
- renderDebtValue({ measures: [] });
-
- expect(screen.getByLabelText('no_data')).toBeInTheDocument();
- expect(screen.getByText('metric.sqale_index.name')).toBeInTheDocument();
-});
-
-function renderDebtValue(props: Partial<DebtValueProps> = {}) {
- return renderComponent(
- <DebtValue
- branchLike={mockMainBranch()}
- component={mockComponent()}
- measures={[
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.sqale_index }) }),
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_technical_debt }) }),
- ]}
- {...props}
- />,
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { screen } from '@testing-library/react';
+import * as React from 'react';
+import { mockBranch } from '../../../../helpers/mocks/branch-like';
+import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates';
+import { mockMetric } from '../../../../helpers/testMocks';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { MetricKey, MetricType } from '../../../../types/metrics';
+import { QualityGateStatusConditionEnhanced } from '../../../../types/quality-gates';
+import QualityGateCondition from '../QualityGateCondition';
+
+it.each([
+ [quickMock(MetricKey.reliability_rating)],
+ [quickMock(MetricKey.security_rating)],
+ [quickMock(MetricKey.sqale_rating)],
+ [quickMock(MetricKey.new_reliability_rating, 'RATING', true)],
+ [quickMock(MetricKey.new_security_rating, 'RATING', true)],
+ [quickMock(MetricKey.new_maintainability_rating, 'RATING', true)],
+ [quickMock(MetricKey.security_hotspots_reviewed)],
+ [quickMock(MetricKey.new_security_hotspots_reviewed, 'RATING', true)],
+])('should render correclty', async (condition) => {
+ renderQualityGateCondition({ condition });
+ expect(
+ await screen.findByText(`metric.${condition.measure.metric.name}.name`),
+ ).toBeInTheDocument();
+
+ expect(
+ await screen.findByText(`quality_gates.operator.${condition.op}`, { exact: false }),
+ ).toBeInTheDocument();
+ // if (condition.measure.metric.type === 'RATING') {
+ // expect(await screen.findByText('.rating', { exact: false })).toBeInTheDocument();
+ // }
+});
+
+it('should show the count when metric is not rating', async () => {
+ renderQualityGateCondition({ condition: quickMock(MetricKey.open_issues, MetricType.Integer) });
+ expect(await screen.findByText('3 metric.open_issues.name')).toBeInTheDocument();
+});
+
+it('should work with branch', async () => {
+ const condition = quickMock(MetricKey.new_maintainability_rating);
+ renderQualityGateCondition({ branchLike: mockBranch(), condition });
+
+ expect(await screen.findByText('metric.new_maintainability_rating.name')).toBeInTheDocument();
+ expect(
+ await screen.findByText('quality_gates.operator.GT.rating', { exact: false }),
+ ).toBeInTheDocument();
+});
+
+function renderQualityGateCondition(props: Partial<QualityGateCondition['props']>) {
+ return renderComponent(
+ <QualityGateCondition
+ component={{ key: 'abcd-key' }}
+ condition={mockQualityGateStatusConditionEnhanced()}
+ {...props}
+ />,
+ );
+}
+
+function quickMock(
+ metric: MetricKey,
+ type = 'RATING',
+ addPeriod = false,
+): QualityGateStatusConditionEnhanced {
+ return mockQualityGateStatusConditionEnhanced({
+ error: '1',
+ measure: {
+ metric: mockMetric({
+ key: metric,
+ name: metric,
+ type,
+ }),
+ value: '3',
+ ...(addPeriod ? { period: { value: '3', index: 1 } } : {}),
+ },
+ metric,
+ ...(addPeriod ? { period: 1 } : {}),
+ });
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import * as React from 'react';
+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 { QualityGateStatusConditionEnhanced } from '../../../../types/quality-gates';
+import { QualityGateConditions, QualityGateConditionsProps } from '../QualityGateConditions';
+
+const ALL_CONDITIONS = 10;
+const HALF_CONDITIONS = 5;
+
+it('should render correctly', async () => {
+ renderQualityGateConditions();
+ expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(ALL_CONDITIONS);
+
+ expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength(
+ ALL_CONDITIONS,
+ );
+});
+
+it('should be collapsible', async () => {
+ renderQualityGateConditions({ collapsible: true });
+ const user = userEvent.setup();
+
+ expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(HALF_CONDITIONS);
+ expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength(
+ HALF_CONDITIONS,
+ );
+
+ await user.click(screen.getByRole('link', { name: 'show_more' }));
+
+ expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(ALL_CONDITIONS);
+ expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength(
+ ALL_CONDITIONS,
+ );
+});
+
+function renderQualityGateConditions(props: Partial<QualityGateConditionsProps> = {}) {
+ const conditions: QualityGateStatusConditionEnhanced[] = [];
+ for (let i = ALL_CONDITIONS; i > 0; --i) {
+ conditions.push(
+ mockQualityGateStatusConditionEnhanced({
+ measure: mockMeasureEnhanced({ metric: mockMetric({ key: i.toString() }) }),
+ }),
+ );
+ }
+
+ return renderComponent(
+ <QualityGateConditions component={mockComponent()} failedConditions={conditions} {...props} />,
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { screen } from '@testing-library/react';
+import React from 'react';
+import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates';
+import { mockMetric } from '../../../../helpers/testMocks';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { MetricKey, MetricType } from '../../../../types/metrics';
+import { QualityGateStatusConditionEnhanced } from '../../../../types/quality-gates';
+import QualityGateCondition from '../QualityGateCondition';
+import QualityGateSimplifiedCondition from '../QualityGateSimplifiedCondition';
+
+it('should show simplified condition', async () => {
+ renderQualityGateCondition({
+ condition: quickMock(MetricKey.new_violations, MetricType.Integer),
+ });
+ expect(await screen.findByText('metric.new_violations.name')).toBeInTheDocument();
+});
+
+function renderQualityGateCondition(props: Partial<QualityGateCondition['props']>) {
+ return renderComponent(
+ <QualityGateSimplifiedCondition
+ component={{ key: 'abcd-key' }}
+ condition={mockQualityGateStatusConditionEnhanced()}
+ {...props}
+ />,
+ );
+}
+
+function quickMock(
+ metric: MetricKey,
+ type = MetricType.Rating,
+ addPeriod = false,
+ value = '3',
+): QualityGateStatusConditionEnhanced {
+ return mockQualityGateStatusConditionEnhanced({
+ error: '1',
+ measure: {
+ metric: mockMetric({
+ key: metric,
+ name: metric,
+ type,
+ }),
+ value,
+ ...(addPeriod ? { period: { value, index: 1 } } : {}),
+ },
+ metric,
+ ...(addPeriod ? { period: 1 } : {}),
+ });
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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 { screen } from '@testing-library/react';
+import * as React from 'react';
+import { mockQualityGateStatusCondition } from '../../../../helpers/mocks/quality-gates';
+import { mockCurrentUser } from '../../../../helpers/testMocks';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { MetricKey } from '../../../../types/metrics';
+import { SonarLintPromotion, SonarLintPromotionProps } from '../SonarLintPromotion';
+
+it('should render correctly', () => {
+ renderSonarLintPromotion();
+ expect(
+ screen.queryByText('overview.fix_failed_conditions_with_sonarlint'),
+ ).not.toBeInTheDocument();
+
+ renderSonarLintPromotion({ currentUser: mockCurrentUser({ usingSonarLintConnectedMode: true }) });
+ expect(
+ screen.queryByText('overview.fix_failed_conditions_with_sonarlint'),
+ ).not.toBeInTheDocument();
+});
+
+it.each(
+ [
+ MetricKey.new_blocker_violations,
+ MetricKey.new_critical_violations,
+ MetricKey.new_info_violations,
+ MetricKey.new_violations,
+ MetricKey.new_major_violations,
+ MetricKey.new_minor_violations,
+ MetricKey.new_code_smells,
+ MetricKey.new_bugs,
+ MetricKey.new_vulnerabilities,
+ MetricKey.new_security_rating,
+ MetricKey.new_maintainability_rating,
+ MetricKey.new_reliability_rating,
+ ].map(Array.of),
+)('should show message for %s', async (metric) => {
+ renderSonarLintPromotion({
+ qgConditions: [mockQualityGateStatusCondition({ metric: metric as MetricKey })],
+ });
+
+ expect(
+ await screen.findByText('overview.fix_failed_conditions_with_sonarlint'),
+ ).toBeInTheDocument();
+});
+
+function renderSonarLintPromotion(props: Partial<SonarLintPromotionProps> = {}) {
+ return renderComponent(<SonarLintPromotion currentUser={mockCurrentUser()} {...props} />);
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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-2024 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 styled from '@emotion/styled';
-import { Badge, ButtonSecondary, themeBorder, themeColor } from 'design-system';
-import React from 'react';
-import { useIntl } from 'react-intl';
-import {
- DEFAULT_ISSUES_QUERY,
- isIssueMeasure,
- propsToIssueParams,
-} from '../../../components/shared/utils';
-import { getBranchLikeQuery } from '../../../helpers/branch-like';
-import { getLocalizedMetricName, translate } 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 { 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),
- );
-
- return (
- <ul className="sw-flex sw-items-center sw-gap-2 sw-flex-wrap sw-mb-4">
- {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 (
- <StyledConditionButton className="sw-px-3 sw-py-2 sw-rounded-1 sw-body-sm" to={url}>
- <Badge className="sw-mr-2 sw-px-1" variant="deleted">
- {translate('overview.measures.failed_badge')}
- </Badge>
- <SpanDanger>
- <FailedMetric condition={condition} />
- </SpanDanger>
- </StyledConditionButton>
- );
-}
-
-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,
- actual,
- measure: {
- metric: { type, domain },
- },
- } = condition;
- const intl = useIntl();
-
- return (
- <>
- {intl.formatMessage(
- { id: 'overview.failed_condition.x_rating_required' },
- {
- rating: `${intl.formatMessage({
- id: `metric_domain.${domain}`,
- })} ${intl.formatMessage({ id: 'metric.type.RATING' }).toLowerCase()}`,
- value: <strong className="sw-body-sm-highlight">{formatMeasure(actual, type)}</strong>,
- threshold: formatMeasure(error, type),
- },
- )}
- </>
- );
-}
-
-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: (
- <>
- {condition.op === 'GT' ? <>≤</> : <>≥</>}{' '}
- {formatMeasure(error, getShortType(metric.type), measureFormattingOptions)}
- </>
- ),
- },
- )}
- </>
- );
-}
-
-function getQGConditionUrl(
- componentKey: string,
- condition: QualityGateStatusConditionEnhanced,
- branchLike?: BranchLike,
-) {
- const { metric } = condition;
- const sinceLeakPeriod = isDiffMetric(metric);
- const ratingIssueType = RATING_METRICS_MAPPING[metric];
-
- if (ratingIssueType) {
- if (ratingIssueType === IssueType.SecurityHotspot) {
- return getComponentSecurityHotspotsUrl(componentKey, {
- ...getBranchLikeQuery(branchLike),
- ...(sinceLeakPeriod ? { sinceLeakPeriod: 'true' } : {}),
- });
- }
- return getComponentIssuesUrl(componentKey, {
- ...DEFAULT_ISSUES_QUERY,
- types: ratingIssueType,
- ...getBranchLikeQuery(branchLike),
- ...(sinceLeakPeriod ? { sinceLeakPeriod: 'true' } : {}),
- ...(ratingIssueType !== IssueType.CodeSmell
- ? { severities: RATING_TO_SEVERITIES_MAPPING[Number(condition.error) - 1] }
- : {}),
- });
- }
-
- if (isIssueMeasure(condition.measure.metric.key)) {
- return getComponentIssuesUrl(componentKey, {
- ...propsToIssueParams(condition.measure.metric.key, condition.period != null),
- ...getBranchLikeQuery(branchLike),
- });
- }
-
- return getComponentDrilldownUrl({
- componentKey,
- metric,
- branchLike,
- listView: true,
- });
-}
-
-const StyledConditionButton = styled(ButtonSecondary)`
- --border: ${themeBorder('default')};
-`;
-
-const SpanDanger = styled.span`
- color: ${themeColor('danger')};
-`;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { DrilldownLink, HelperHintIcon, LightLabel } from 'design-system';
-import * as React from 'react';
-import HelpTooltip from '../../../components/controls/HelpTooltip';
-import Tooltip from '../../../components/controls/Tooltip';
-import { getLeakValue } from '../../../components/measure/utils';
-import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
-import { getBranchLikeQuery } from '../../../helpers/branch-like';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { findMeasure, formatMeasure, localizeMetric } from '../../../helpers/measures';
-import { getComponentIssuesUrl, getComponentSecurityHotspotsUrl } from '../../../helpers/urls';
-import { BranchLike } from '../../../types/branch-like';
-import { ComponentQualifier } from '../../../types/component';
-import { IssueType } from '../../../types/issues';
-import { MetricType } from '../../../types/metrics';
-import { Component, MeasureEnhanced } from '../../../types/types';
-import { getIssueMetricKey } from '../utils';
-import { OverviewDisabledLinkTooltip } from './OverviewDisabledLinkTooltip';
-
-export interface IssueLabelProps {
- branchLike?: BranchLike;
- component: Component;
- helpTooltip?: string;
- measures: MeasureEnhanced[];
- type: IssueType;
- useDiffMetric?: boolean;
-}
-
-export function IssueLabel(props: IssueLabelProps) {
- const { branchLike, component, helpTooltip, measures, type, useDiffMetric = false } = props;
- const metricKey = getIssueMetricKey(type, useDiffMetric);
- const measure = findMeasure(measures, metricKey);
-
- let value;
-
- if (measure) {
- value = useDiffMetric ? getLeakValue(measure) : measure.value;
- }
-
- const params = {
- ...getBranchLikeQuery(branchLike),
- inNewCodePeriod: useDiffMetric ? 'true' : 'false',
- ...DEFAULT_ISSUES_QUERY,
- types: type,
- };
-
- const url =
- type === IssueType.SecurityHotspot
- ? getComponentSecurityHotspotsUrl(component.key, params)
- : getComponentIssuesUrl(component.key, params);
-
- const disabled =
- component.qualifier === ComponentQualifier.Application && component.needIssueSync;
-
- const drilldownLinkProps = disabled
- ? { disabled, to: '' }
- : {
- 'aria-label': translateWithParameters(
- 'overview.see_list_of_x_y_issues',
- value as string,
- localizeMetric(metricKey),
- ),
- to: url,
- };
-
- return (
- <div className="sw-body-md sw-flex sw-items-center">
- {value === undefined ? (
- <LightLabel aria-label={translate('no_data')}> — </LightLabel>
- ) : (
- <Tooltip
- classNameSpace={disabled ? 'tooltip' : 'sw-hidden'}
- overlay={<OverviewDisabledLinkTooltip />}
- >
- <DrilldownLink className="it__overview-measures-value" {...drilldownLinkProps}>
- {formatMeasure(value, MetricType.ShortInteger)}
- </DrilldownLink>
- </Tooltip>
- )}
-
- <LightLabel className="sw-mx-2">{localizeMetric(metricKey)}</LightLabel>
-
- {helpTooltip && (
- <HelpTooltip overlay={helpTooltip}>
- <HelperHintIcon aria-label={helpTooltip} />
- </HelpTooltip>
- )}
- </div>
- );
-}
-
-export default React.memo(IssueLabel);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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.
- */
-/* eslint-disable react/no-unused-prop-types */
-
-import { DiscreetLinkBox, MetricsRatingBadge } from 'design-system';
-import * as React from 'react';
-import Tooltip from '../../../components/controls/Tooltip';
-import RatingTooltipContent from '../../../components/measure/RatingTooltipContent';
-import { getLeakValue } from '../../../components/measure/utils';
-import { translateWithParameters } from '../../../helpers/l10n';
-import { findMeasure, formatRating } from '../../../helpers/measures';
-import { getComponentDrilldownUrl } from '../../../helpers/urls';
-import { BranchLike } from '../../../types/branch-like';
-import { IssueType } from '../../../types/issues';
-import { Component, MeasureEnhanced } from '../../../types/types';
-import { getIssueRatingMetricKey } from '../utils';
-
-export interface IssueRatingProps {
- branchLike?: BranchLike;
- component: Component;
- measures: MeasureEnhanced[];
- type: IssueType;
- useDiffMetric?: boolean;
-}
-
-export function IssueRating(props: IssueRatingProps) {
- const { branchLike, component, useDiffMetric = false, measures, type } = props;
- const ratingKey = getIssueRatingMetricKey(type, useDiffMetric);
- const measure = findMeasure(measures, ratingKey);
- const rawValue = measure && (useDiffMetric ? getLeakValue(measure) : measure.value);
- const value = formatRating(rawValue);
-
- if (!ratingKey || !measure) {
- return <NoRating />;
- }
-
- return (
- <Tooltip overlay={rawValue && <RatingTooltipContent metricKey={ratingKey} value={rawValue} />}>
- <span>
- {value ? (
- <DiscreetLinkBox
- to={getComponentDrilldownUrl({
- branchLike,
- componentKey: component.key,
- metric: ratingKey,
- listView: true,
- })}
- >
- <MetricsRatingBadge
- label={translateWithParameters('metric.has_rating_X', value)}
- rating={value}
- size="md"
- />
- </DiscreetLinkBox>
- ) : (
- <NoRating />
- )}
- </span>
- </Tooltip>
- );
-}
-
-export default IssueRating;
-
-function NoRating() {
- return <div className="sw-w-8 sw-h-8 sw-flex sw-justify-center sw-items-center">–</div>;
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { differenceInDays } from 'date-fns';
-import * as React from 'react';
-import { injectIntl, WrappedComponentProps } from 'react-intl';
-import Tooltip from '../../../components/controls/Tooltip';
-import DateFormatter, { longFormatterOption } from '../../../components/intl/DateFormatter';
-import DateFromNow from '../../../components/intl/DateFromNow';
-import DateTimeFormatter, { formatterOption } from '../../../components/intl/DateTimeFormatter';
-import { translateWithParameters } from '../../../helpers/l10n';
-import { getNewCodePeriodDate, getNewCodePeriodLabel } from '../../../helpers/new-code-period';
-import { NewCodeDefinitionType } from '../../../types/new-code-definition';
-import { Dict, Period } from '../../../types/types';
-
-interface Props {
- period: Period;
-}
-
-const MODE_INCLUDES_TIME: Dict<boolean> = {
- manual_baseline: true,
- SPECIFIC_ANALYSIS: true,
-};
-
-export class LeakPeriodLegend extends React.PureComponent<Props & WrappedComponentProps> {
- formatDate = (date: string) => {
- return this.props.intl.formatDate(date, longFormatterOption);
- };
-
- formatDateTime = (date: string) => {
- return this.props.intl.formatTime(date, formatterOption);
- };
-
- render() {
- const { period } = this.props;
- const leakPeriodLabel = getNewCodePeriodLabel(
- period,
- MODE_INCLUDES_TIME[period.mode] ? this.formatDateTime : this.formatDate,
- );
- if (!leakPeriodLabel) {
- return null;
- }
-
- if (period.mode === 'days' || period.mode === NewCodeDefinitionType.NumberOfDays) {
- return (
- <div className="overview-legend overview-legend-spaced-line">
- {translateWithParameters('overview.new_code_period_x', leakPeriodLabel)}
- </div>
- );
- }
-
- const leakPeriodDate = getNewCodePeriodDate(period);
- if (!leakPeriodDate) {
- return null;
- }
-
- const formattedDateFunction = (formattedLeakPeriodDate: string) => (
- <span>
- {translateWithParameters(
- period.mode === 'previous_analysis'
- ? 'overview.previous_analysis_on_x'
- : 'overview.started_on_x',
- formattedLeakPeriodDate,
- )}
- </span>
- );
-
- const tooltip =
- differenceInDays(new Date(), leakPeriodDate) < 1 ? (
- <DateTimeFormatter date={leakPeriodDate}>{formattedDateFunction}</DateTimeFormatter>
- ) : (
- <DateFormatter date={leakPeriodDate} long>
- {formattedDateFunction}
- </DateFormatter>
- );
-
- return (
- <Tooltip overlay={tooltip}>
- <div className="overview-legend">
- {translateWithParameters('overview.new_code_period_x', leakPeriodLabel)}
- <br />
- <DateFromNow date={leakPeriodDate}>
- {(fromNow) => (
- <span className="note">
- {translateWithParameters(
- period.mode === 'previous_analysis'
- ? 'overview.previous_analysis_x'
- : 'overview.started_x',
- fromNow,
- )}
- </span>
- )}
- </DateFromNow>
- </div>
- </Tooltip>
- );
- }
-}
-
-export default injectIntl(LeakPeriodLegend);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { LinkBox, TextMuted } from 'design-system';
-import * as React from 'react';
-import { Path } from 'react-router-dom';
-import IssueTypeIcon from '../../../components/icons/IssueTypeIcon';
-import MeasureIndicator from '../../../components/measure/MeasureIndicator';
-import {
- DEFAULT_ISSUES_QUERY,
- isIssueMeasure,
- propsToIssueParams,
-} from '../../../components/shared/utils';
-import { getBranchLikeQuery } from '../../../helpers/branch-like';
-import { translate } from '../../../helpers/l10n';
-import { formatMeasure, isDiffMetric, localizeMetric } from '../../../helpers/measures';
-import { getOperatorLabel } from '../../../helpers/qualityGates';
-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, Dict } from '../../../types/types';
-import { RATING_TO_SEVERITIES_MAPPING } from '../utils';
-
-interface Props {
- branchLike?: BranchLike;
- component: Pick<Component, 'key'>;
- condition: QualityGateStatusConditionEnhanced;
-}
-
-export default class QualityGateCondition extends React.PureComponent<Props> {
- getIssuesUrl = (inNewCodePeriod: boolean, customQuery: Dict<string>) => {
- const query: Dict<string | undefined> = {
- ...DEFAULT_ISSUES_QUERY,
- ...getBranchLikeQuery(this.props.branchLike),
- ...customQuery,
- };
- if (inNewCodePeriod) {
- Object.assign(query, { inNewCodePeriod: 'true' });
- }
- return getComponentIssuesUrl(this.props.component.key, query);
- };
-
- getUrlForSecurityHotspot(inNewCodePeriod: boolean) {
- const query: Dict<string | undefined> = {
- ...getBranchLikeQuery(this.props.branchLike),
- };
- if (inNewCodePeriod) {
- Object.assign(query, { inNewCodePeriod: 'true' });
- }
- return getComponentSecurityHotspotsUrl(this.props.component.key, query);
- }
-
- getUrlForCodeSmells(inNewCodePeriod: boolean) {
- return this.getIssuesUrl(inNewCodePeriod, { types: 'CODE_SMELL' });
- }
-
- getUrlForBugsOrVulnerabilities(type: string, inNewCodePeriod: boolean) {
- const { condition } = this.props;
- const threshold = condition.level === 'ERROR' ? condition.error : condition.warning;
-
- return this.getIssuesUrl(inNewCodePeriod, {
- types: type,
- severities: RATING_TO_SEVERITIES_MAPPING[Number(threshold) - 1],
- });
- }
-
- wrapWithLink(children: React.ReactNode) {
- const { branchLike, component, condition } = this.props;
-
- const metricKey = condition.measure.metric.key;
-
- const METRICS_TO_URL_MAPPING: Dict<() => Path> = {
- [MetricKey.reliability_rating]: () =>
- this.getUrlForBugsOrVulnerabilities(IssueType.Bug, false),
- [MetricKey.new_reliability_rating]: () =>
- this.getUrlForBugsOrVulnerabilities(IssueType.Bug, true),
- [MetricKey.security_rating]: () =>
- this.getUrlForBugsOrVulnerabilities(IssueType.Vulnerability, false),
- [MetricKey.new_security_rating]: () =>
- this.getUrlForBugsOrVulnerabilities(IssueType.Vulnerability, true),
- [MetricKey.sqale_rating]: () => this.getUrlForCodeSmells(false),
- [MetricKey.new_maintainability_rating]: () => this.getUrlForCodeSmells(true),
- [MetricKey.security_hotspots_reviewed]: () => this.getUrlForSecurityHotspot(false),
- [MetricKey.new_security_hotspots_reviewed]: () => this.getUrlForSecurityHotspot(true),
- };
-
- if (METRICS_TO_URL_MAPPING[metricKey]) {
- return <LinkBox to={METRICS_TO_URL_MAPPING[metricKey]()}>{children}</LinkBox>;
- }
-
- const url = isIssueMeasure(condition.measure.metric.key)
- ? getComponentIssuesUrl(component.key, {
- ...propsToIssueParams(condition.measure.metric.key, condition.period != null),
- ...getBranchLikeQuery(branchLike),
- })
- : getComponentDrilldownUrl({
- componentKey: component.key,
- metric: condition.measure.metric.key,
- branchLike,
- listView: true,
- });
-
- return <LinkBox to={url}>{children}</LinkBox>;
- }
-
- getPrimaryText = () => {
- const { condition } = this.props;
- const { measure } = condition;
- const { metric } = measure;
- const isDiff = isDiffMetric(metric.key);
-
- const subText =
- !isDiff && condition.period != null
- ? `${localizeMetric(metric.key)} ${translate('quality_gates.conditions.new_code')}`
- : localizeMetric(metric.key);
-
- if (metric.type !== MetricType.Rating) {
- const actual = (condition.period ? measure.period?.value : measure.value) as string;
- const formattedValue = formatMeasure(actual, metric.type, {
- decimal: 2,
- omitExtraDecimalZeros: metric.type === MetricType.Percent,
- });
- return `${formattedValue} ${subText}`;
- }
-
- return subText;
- };
-
- render() {
- const { condition } = this.props;
- const { measure } = condition;
- const { metric } = measure;
-
- const threshold = (condition.level === 'ERROR' ? condition.error : condition.warning) as string;
- const actual = (condition.period ? measure.period?.value : measure.value) as string;
-
- const operator = getOperatorLabel(condition.op, metric);
-
- return this.wrapWithLink(
- <div className="sw-flex sw-items-center sw-p-2">
- <MeasureIndicator
- className="sw-flex sw-justify-center sw-w-6 sw-mx-4"
- decimals={2}
- metricKey={measure.metric.key}
- metricType={measure.metric.type}
- value={actual}
- />
- <div className="sw-flex sw-flex-col sw-text-sm">
- <div className="sw-flex sw-items-center">
- <IssueTypeIcon className="sw-mr-2" query={metric.key} />
- <span className="sw-body-sm-highlight sw-text-ellipsis sw-max-w-abs-300">
- {this.getPrimaryText()}
- </span>
- </div>
- <TextMuted text={`${operator} ${formatMeasure(threshold, metric.type)}`} />
- </div>
- </div>,
- );
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { BasicSeparator, Link } from 'design-system';
-import { sortBy } from 'lodash';
-import * as React from 'react';
-import { translate } from '../../../helpers/l10n';
-import { BranchLike } from '../../../types/branch-like';
-import { MetricKey } from '../../../types/metrics';
-import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
-import { Component } from '../../../types/types';
-import QualityGateCondition from './QualityGateCondition';
-import QualityGateSimplifiedCondition from './QualityGateSimplifiedCondition';
-
-const LEVEL_ORDER = ['ERROR', 'WARN'];
-
-export interface QualityGateConditionsProps {
- branchLike?: BranchLike;
- component: Pick<Component, 'key'>;
- collapsible?: boolean;
- failedConditions: QualityGateStatusConditionEnhanced[];
- isBuiltInQualityGate?: boolean;
-}
-
-const MAX_CONDITIONS = 5;
-
-export function QualityGateConditions(props: QualityGateConditionsProps) {
- const { branchLike, collapsible, component, failedConditions, isBuiltInQualityGate } = props;
- const [collapsed, toggleCollapsed] = React.useState(Boolean(collapsible));
-
- const handleToggleCollapsed = React.useCallback(() => toggleCollapsed(!collapsed), [collapsed]);
-
- const isSimplifiedCondition = React.useCallback(
- (condition: QualityGateStatusConditionEnhanced) => {
- const { metric } = condition.measure;
- return metric.key === MetricKey.new_violations && isBuiltInQualityGate;
- },
- [isBuiltInQualityGate],
- );
-
- const sortedConditions = sortBy(failedConditions, (condition) =>
- LEVEL_ORDER.indexOf(condition.level),
- );
-
- let renderConditions;
- let renderCollapsed;
-
- if (collapsed && sortedConditions.length > MAX_CONDITIONS) {
- renderConditions = sortedConditions.slice(0, MAX_CONDITIONS);
- renderCollapsed = true;
- } else {
- renderConditions = sortedConditions;
- renderCollapsed = false;
- }
-
- return (
- <ul id="overview-quality-gate-conditions-list" className="sw-mb-2">
- {renderConditions.map((condition) => (
- <div key={condition.measure.metric.key}>
- {isSimplifiedCondition(condition) ? (
- <QualityGateSimplifiedCondition
- branchLike={branchLike}
- component={component}
- condition={condition}
- />
- ) : (
- <QualityGateCondition
- branchLike={branchLike}
- component={component}
- condition={condition}
- />
- )}
- <BasicSeparator />
- </div>
- ))}
- {renderCollapsed && (
- <li className="sw-flex sw-justify-center sw-my-3">
- <Link onClick={handleToggleCollapsed} to={{}} preventDefault>
- <span className="sw-font-semibold sw-text-sm">{translate('show_more')}</span>
- </Link>
- </li>
- )}
- </ul>
- );
-}
-
-export default React.memo(QualityGateConditions);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { Highlight, LinkBox } from 'design-system';
-import * as React from 'react';
-import { propsToIssueParams } from '../../../components/shared/utils';
-import { getBranchLikeQuery } from '../../../helpers/branch-like';
-import { translate } from '../../../helpers/l10n';
-import { formatMeasure, isDiffMetric, localizeMetric } from '../../../helpers/measures';
-import { getComponentIssuesUrl } from '../../../helpers/urls';
-import { BranchLike } from '../../../types/branch-like';
-import { MetricKey, MetricType } from '../../../types/metrics';
-import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
-import { Component } from '../../../types/types';
-
-interface Props {
- branchLike?: BranchLike;
- component: Pick<Component, 'key'>;
- condition: QualityGateStatusConditionEnhanced;
-}
-
-export default function QualityGateSimplifiedCondition({
- branchLike,
- component,
- condition,
-}: Readonly<Props>) {
- const getPrimaryText = () => {
- const { measure } = condition;
- const { metric } = measure;
- const isDiff = isDiffMetric(metric.key);
-
- const subText =
- !isDiff && condition.period != null
- ? `${localizeMetric(metric.key)} ${translate('quality_gates.conditions.new_code')}`
- : localizeMetric(metric.key);
-
- return subText;
- };
-
- const { measure } = condition;
- const { metric } = measure;
-
- const value = (condition.period ? measure.period?.value : measure.value) as string;
-
- const formattedValue = formatMeasure(value, MetricType.ShortInteger, {
- decimals: 0,
- omitExtraDecimalZeros: metric.type === MetricType.Percent,
- });
-
- return (
- <LinkBox
- to={getComponentIssuesUrl(component.key, {
- ...propsToIssueParams(condition.measure.metric.key, condition.period != null),
- ...getBranchLikeQuery(branchLike),
- })}
- >
- <div className="sw-flex sw-p-2 sw-items-baseline">
- <Highlight className="sw-mx-4 sw-w-6 sw-my-0 sw-text-right">{formattedValue}</Highlight>
- <Highlight
- className="sw-text-ellipsis sw-pr-4"
- data-guiding-id={
- metric.key === MetricKey.new_violations
- ? 'overviewZeroNewIssuesSimplification'
- : undefined
- }
- >
- {getPrimaryText()}
- </Highlight>
- </div>
- </LinkBox>
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { QualityGateIndicator, TextError } from 'design-system';
-import React from 'react';
-import { useIntl } from 'react-intl';
-import { translate } from '../../../helpers/l10n';
-import { Status } from '../../../types/types';
-
-interface Props {
- status: Status;
- failedConditionCount: number;
-}
-
-export default function QualityGateStatusHeader(props: Props) {
- const { status, failedConditionCount } = props;
- const intl = useIntl();
-
- return (
- <div className="sw-flex sw-items-center sw-mb-4">
- <QualityGateIndicator status={status} className="sw-mr-2" size="xl" />
- <div className="sw-flex sw-flex-col">
- <span className="sw-heading-lg">{translate('metric.level', status)}</span>
- {failedConditionCount > 0 && (
- <TextError
- className="sw-font-regular"
- text={intl.formatMessage(
- { id: 'overview.X_conditions_failed' },
- {
- conditions: <strong>{failedConditionCount}</strong>,
- },
- )}
- />
- )}
- </div>
- </div>
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { OverviewQGPassedIcon } from 'design-system';
-import React from 'react';
-import { translate } from '../../../helpers/l10n';
-
-export default function QualityGateStatusPassedView() {
- return (
- <div className="sw-flex sw-items-center sw-justify-center sw-flex-col">
- <OverviewQGPassedIcon className="sw-my-12" />
- <p className="sw-mb-8">{translate('overview.passed.clean_code')}</p>
- </div>
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { BasicSeparator, HelperHintIcon, PageTitle } from 'design-system';
-import React from 'react';
-import HelpTooltip from '../../../components/controls/HelpTooltip';
-import { translate } from '../../../helpers/l10n';
-
-export function QualityGateStatusTitle() {
- return (
- <>
- <div className="sw-flex sw-items-center sw-mb-4 sw--mt-2">
- <div className="sw-flex sw-items-center">
- <PageTitle as="h2" text={translate('overview.quality_gate.status')} />
- <HelpTooltip
- className="sw-ml-2"
- overlay={<div className="sw-my-4">{translate('overview.quality_gate.help')}</div>}
- >
- <HelperHintIcon aria-label="help-tooltip" />
- </HelpTooltip>
- </div>
- </div>
- <BasicSeparator className="sw--mx-6" />
- </>
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { Card, DiscreetLink } from 'design-system';
-import * as React from 'react';
-import { FormattedMessage } from 'react-intl';
-import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
-import SonarLintIcon from '../../../components/icons/SonarLintIcon';
-import { translate } from '../../../helpers/l10n';
-import { MetricKey } from '../../../types/metrics';
-import { QualityGateStatusCondition } from '../../../types/quality-gates';
-import { CurrentUser } from '../../../types/users';
-
-export interface SonarLintPromotionProps {
- currentUser: CurrentUser;
- qgConditions?: QualityGateStatusCondition[];
-}
-
-const CONDITIONS_TO_SHOW = [
- MetricKey.new_blocker_violations,
- MetricKey.new_critical_violations,
- MetricKey.new_info_violations,
- MetricKey.new_violations,
- MetricKey.new_major_violations,
- MetricKey.new_minor_violations,
- MetricKey.new_code_smells,
- MetricKey.new_bugs,
- MetricKey.new_vulnerabilities,
- MetricKey.new_security_rating,
- MetricKey.new_maintainability_rating,
- MetricKey.new_reliability_rating,
-];
-
-export function SonarLintPromotion({ currentUser, qgConditions }: SonarLintPromotionProps) {
- const showMessage = qgConditions?.some(
- (qgCondition) =>
- CONDITIONS_TO_SHOW.includes(qgCondition.metric) && qgCondition.level === 'ERROR',
- );
- if (!showMessage || currentUser.usingSonarLintConnectedMode) {
- return null;
- }
- return (
- <Card className="it__overview__sonarlint-promotion sw-my-4 sw-body-sm">
- <FormattedMessage
- id="overview.fix_failed_conditions_with_sonarlint"
- defaultMessage={translate('overview.fix_failed_conditions_with_sonarlint')}
- values={{
- link: (
- <>
- <DiscreetLink
- to="https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=sonarqube"
- rel="noopener noreferrer"
- target="_blank"
- showExternalIcon={false}
- className="sw-mr-1"
- >
- SonarLint
- </DiscreetLink>
- <span className="sw-align-middle">
- <SonarLintIcon size={16} />
- </span>
- </>
- ),
- }}
- />
- </Card>
- );
-}
-
-export default withCurrentUserContext(SonarLintPromotion);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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
- const maintainabilityRatingLink = byRole('link', {
- name: 'overview.measures.failed_badge overview.failed_condition.x_rating_requiredmetric_domain.Maintainability metric.type.rating E A',
- }).get();
- expect(maintainabilityRatingLink).toBeInTheDocument();
- expect(maintainabilityRatingLink).toHaveAttribute(
- 'href',
- '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=CODE_SMELL&pullRequest=1001&sinceLeakPeriod=true&id=my-project',
- );
-
- // Security Hotspots rating condition
- const securityHotspotsRatingLink = byRole('link', {
- name: 'overview.measures.failed_badge overview.failed_condition.x_rating_requiredmetric_domain.Security Review metric.type.rating E A',
- }).get();
- expect(securityHotspotsRatingLink).toBeInTheDocument();
- expect(securityHotspotsRatingLink).toHaveAttribute(
- 'href',
- '/security_hotspots?id=my-project&pullRequest=1001',
- );
-
- // New code smells
- const codeSmellsLink = byRole('link', {
- name: 'overview.measures.failed_badge overview.failed_condition.x_required 5 Code Smells≤ 1',
- }).get();
- expect(codeSmellsLink).toBeInTheDocument();
- expect(codeSmellsLink).toHaveAttribute(
- 'href',
- '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=CODE_SMELL&pullRequest=1001&id=my-project',
- );
-
- // Conditions to cover
- const conditionToCoverLink = byRole('link', {
- name: 'overview.measures.failed_badge overview.failed_condition.x_required 5 Conditions to cover≥ 10',
- }).get();
- expect(conditionToCoverLink).toBeInTheDocument();
- expect(conditionToCoverLink).toHaveAttribute(
- 'href',
- '/component_measures?id=my-project&metric=conditions_to_cover&pullRequest=1001&view=list',
- );
-
- 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}
- />,
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { screen } from '@testing-library/react';
-import * as React from 'react';
-import { mockPullRequest } from '../../../../helpers/mocks/branch-like';
-import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks';
-import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-import { ComponentQualifier } from '../../../../types/component';
-import { IssueType } from '../../../../types/issues';
-import { MetricKey } from '../../../../types/metrics';
-import { IssueLabel, IssueLabelProps } from '../IssueLabel';
-
-it('should render correctly for bugs', async () => {
- const measures = [
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.bugs }) }),
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_bugs }) }),
- ];
-
- const rtl = renderIssueLabel({ measures });
- expect(
- await screen.findByRole('link', {
- name: 'overview.see_list_of_x_y_issues.1.0.metric.bugs.name',
- }),
- ).toBeInTheDocument();
-
- rtl.unmount();
-
- renderIssueLabel({ measures, useDiffMetric: true });
-
- expect(
- await screen.findByRole('link', {
- name: 'overview.see_list_of_x_y_issues.1.0.metric.new_bugs.name',
- }),
- ).toBeInTheDocument();
-});
-
-it('should render correctly for hotspots with tooltip', async () => {
- const helpTooltip = 'tooltip text';
- const type = IssueType.SecurityHotspot;
- const measures = [
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.security_hotspots }) }),
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_security_hotspots }) }),
- ];
-
- renderIssueLabel({
- helpTooltip,
- measures,
- type,
- });
-
- expect(
- await screen.findByRole('link', {
- name: 'overview.see_list_of_x_y_issues.1.0.metric.security_hotspots.name',
- }),
- ).toBeInTheDocument();
-
- expect(screen.getByText('tooltip text')).toBeInTheDocument();
-});
-
-it('should render correctly for a re-indexing Application', () => {
- const type = IssueType.SecurityHotspot;
- const measures = [
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.security_hotspots }) }),
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_security_hotspots }) }),
- ];
-
- renderIssueLabel({
- component: mockComponent({ needIssueSync: true, qualifier: ComponentQualifier.Application }),
- measures,
- type,
- });
-
- expect(
- screen.queryByRole('link', {
- name: 'overview.see_list_of_x_y_issues.1.0.metric.security_hotspots.name',
- }),
- ).not.toBeInTheDocument();
-});
-
-function renderIssueLabel(props: Partial<IssueLabelProps> = {}) {
- return renderComponent(
- <IssueLabel
- branchLike={mockPullRequest()}
- component={mockComponent()}
- measures={[]}
- type={IssueType.Bug}
- {...props}
- />,
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { screen } from '@testing-library/react';
-import * as React from 'react';
-import { mockPullRequest } from '../../../../helpers/mocks/branch-like';
-import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks';
-import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-import { IssueType } from '../../../../types/issues';
-import { MetricKey } from '../../../../types/metrics';
-import { IssueRating, IssueRatingProps } from '../IssueRating';
-
-it('should render correctly for vulnerabilities', async () => {
- renderIssueRating({ type: IssueType.Vulnerability, useDiffMetric: true });
- expect(await screen.findByLabelText('metric.has_rating_X.A')).toBeInTheDocument();
- expect(await screen.findByText('metric.security_rating.tooltip.A')).toBeInTheDocument();
-});
-
-it('should render correctly if no values are present', async () => {
- renderIssueRating({
- measures: [mockMeasureEnhanced({ metric: mockMetric({ key: 'NONE' }) })],
- });
- expect(await screen.findByText('–')).toBeInTheDocument();
-});
-
-function renderIssueRating(props: Partial<IssueRatingProps> = {}) {
- return renderComponent(
- <IssueRating
- branchLike={mockPullRequest()}
- component={mockComponent()}
- measures={[
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_reliability_rating }) }),
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.reliability_rating }) }),
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_maintainability_rating }) }),
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.sqale_rating }) }),
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.new_security_rating }) }),
- mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.security_rating }) }),
- ]}
- type={IssueType.Bug}
- {...props}
- />,
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { screen } from '@testing-library/react';
-import { differenceInDays } from 'date-fns';
-import * as React from 'react';
-import { IntlShape } from 'react-intl';
-import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-import { Period } from '../../../../types/types';
-import { LeakPeriodLegend } from '../LeakPeriodLegend';
-
-jest.mock('date-fns', () => {
- const actual = jest.requireActual('date-fns');
- return {
- ...actual,
- differenceInDays: jest.fn().mockReturnValue(10),
- differenceInYears: jest.fn().mockReturnValue(-9),
- };
-});
-
-it('10 days', async () => {
- renderLeakPeriodLegend({ mode: 'days', parameter: '10' });
-
- expect(
- await screen.findByText('overview.new_code_period_x.overview.period.days.10'),
- ).toBeInTheDocument();
-});
-
-it('date', async () => {
- renderLeakPeriodLegend({ mode: 'date', parameter: '2013-01-01' });
-
- expect(
- await screen.findByText('overview.new_code_period_x.overview.period.date.formatted.2013-01-01'),
- ).toBeInTheDocument();
- expect(await screen.findByText('overview.started_x.9 years ago')).toBeInTheDocument();
- expect(await screen.findByText(/overview\.started_on_x\..*/)).toBeInTheDocument();
-});
-
-it('version', async () => {
- renderLeakPeriodLegend({ mode: 'version', parameter: '0.1' });
-
- expect(
- await screen.findByText('overview.new_code_period_x.overview.period.version.0.1'),
- ).toBeInTheDocument();
- expect(await screen.findByText(/overview\.started_x\..*/)).toBeInTheDocument();
- expect(await screen.findByText(/overview\.started_on_x\..*/)).toBeInTheDocument();
-});
-
-it('previous_version', async () => {
- renderLeakPeriodLegend({ mode: 'previous_version' });
-
- expect(
- await screen.findByText(
- 'overview.new_code_period_x.overview.period.previous_version_only_date',
- ),
- ).toBeInTheDocument();
- expect(await screen.findByText(/overview\.started_x\..*/)).toBeInTheDocument();
- expect(await screen.findByText(/overview\.started_on_x\..*/)).toBeInTheDocument();
-});
-
-it('previous_analysis', async () => {
- renderLeakPeriodLegend({ mode: 'previous_analysis' });
-
- expect(
- await screen.findByText('overview.new_code_period_x.overview.period.previous_analysis.'),
- ).toBeInTheDocument();
- expect(await screen.findByText(/overview\.previous_analysis_x\..*/)).toBeInTheDocument();
- expect(await screen.findByText(/overview\.previous_analysis_x\..*/)).toBeInTheDocument();
-});
-
-it('manual_baseline', async () => {
- const rtl = renderLeakPeriodLegend({ mode: 'manual_baseline' });
-
- expect(
- await screen.findByText(
- /overview\.new_code_period_x\.overview\.period\.manual_baseline\.formattedTime\..*/,
- ),
- ).toBeInTheDocument();
- expect(await screen.findByText(/overview\.started_x\..*/)).toBeInTheDocument();
- expect(await screen.findByText(/overview\.started_on_x\..*/)).toBeInTheDocument();
-
- rtl.unmount();
- renderLeakPeriodLegend({ mode: 'manual_baseline', parameter: '1.1.2' });
-
- expect(
- await screen.findByText('overview.new_code_period_x.overview.period.manual_baseline.1.1.2'),
- ).toBeInTheDocument();
- expect(
- await screen.findByText('overview.new_code_period_x.overview.period.manual_baseline.1.1.2'),
- ).toBeInTheDocument();
-});
-
-it('should render a more precise date', async () => {
- (differenceInDays as jest.Mock<any>).mockReturnValueOnce(0);
-
- renderLeakPeriodLegend({ date: '2018-08-17T00:00:00+0200', mode: 'previous_version' });
-
- expect(
- await screen.findByText(
- 'overview.new_code_period_x.overview.period.previous_version_only_date',
- ),
- ).toBeInTheDocument();
- expect(await screen.findByText(/overview\.started_x\..*/)).toBeInTheDocument();
- expect(await screen.findByText(/overview\.started_on_x\..*/)).toBeInTheDocument();
-});
-
-function renderLeakPeriodLegend(period: Partial<Period> = {}) {
- return renderComponent(
- <LeakPeriodLegend
- intl={
- {
- formatDate: (date: string) => 'formatted.' + date,
- formatTime: (date: string) => 'formattedTime.' + date,
- } as IntlShape
- }
- period={{
- date: '2013-09-22T00:00:00+0200',
- index: 0,
- mode: 'version',
- ...period,
- }}
- />,
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { screen } from '@testing-library/react';
-import * as React from 'react';
-import { mockBranch } from '../../../../helpers/mocks/branch-like';
-import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates';
-import { mockMetric } from '../../../../helpers/testMocks';
-import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-import { MetricKey, MetricType } from '../../../../types/metrics';
-import { QualityGateStatusConditionEnhanced } from '../../../../types/quality-gates';
-import QualityGateCondition from '../QualityGateCondition';
-
-it.each([
- [quickMock(MetricKey.reliability_rating)],
- [quickMock(MetricKey.security_rating)],
- [quickMock(MetricKey.sqale_rating)],
- [quickMock(MetricKey.new_reliability_rating, 'RATING', true)],
- [quickMock(MetricKey.new_security_rating, 'RATING', true)],
- [quickMock(MetricKey.new_maintainability_rating, 'RATING', true)],
- [quickMock(MetricKey.security_hotspots_reviewed)],
- [quickMock(MetricKey.new_security_hotspots_reviewed, 'RATING', true)],
-])('should render correclty', async (condition) => {
- renderQualityGateCondition({ condition });
- expect(
- await screen.findByText(`metric.${condition.measure.metric.name}.name`),
- ).toBeInTheDocument();
-
- expect(
- await screen.findByText(`quality_gates.operator.${condition.op}`, { exact: false }),
- ).toBeInTheDocument();
- // if (condition.measure.metric.type === 'RATING') {
- // expect(await screen.findByText('.rating', { exact: false })).toBeInTheDocument();
- // }
-});
-
-it('should show the count when metric is not rating', async () => {
- renderQualityGateCondition({ condition: quickMock(MetricKey.open_issues, MetricType.Integer) });
- expect(await screen.findByText('3 metric.open_issues.name')).toBeInTheDocument();
-});
-
-it('should work with branch', async () => {
- const condition = quickMock(MetricKey.new_maintainability_rating);
- renderQualityGateCondition({ branchLike: mockBranch(), condition });
-
- expect(await screen.findByText('metric.new_maintainability_rating.name')).toBeInTheDocument();
- expect(
- await screen.findByText('quality_gates.operator.GT.rating', { exact: false }),
- ).toBeInTheDocument();
-});
-
-function renderQualityGateCondition(props: Partial<QualityGateCondition['props']>) {
- return renderComponent(
- <QualityGateCondition
- component={{ key: 'abcd-key' }}
- condition={mockQualityGateStatusConditionEnhanced()}
- {...props}
- />,
- );
-}
-
-function quickMock(
- metric: MetricKey,
- type = 'RATING',
- addPeriod = false,
-): QualityGateStatusConditionEnhanced {
- return mockQualityGateStatusConditionEnhanced({
- error: '1',
- measure: {
- metric: mockMetric({
- key: metric,
- name: metric,
- type,
- }),
- value: '3',
- ...(addPeriod ? { period: { value: '3', index: 1 } } : {}),
- },
- metric,
- ...(addPeriod ? { period: 1 } : {}),
- });
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import * as React from 'react';
-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 { QualityGateStatusConditionEnhanced } from '../../../../types/quality-gates';
-import { QualityGateConditions, QualityGateConditionsProps } from '../QualityGateConditions';
-
-const ALL_CONDITIONS = 10;
-const HALF_CONDITIONS = 5;
-
-it('should render correctly', async () => {
- renderQualityGateConditions();
- expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(ALL_CONDITIONS);
-
- expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength(
- ALL_CONDITIONS,
- );
-});
-
-it('should be collapsible', async () => {
- renderQualityGateConditions({ collapsible: true });
- const user = userEvent.setup();
-
- expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(HALF_CONDITIONS);
- expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength(
- HALF_CONDITIONS,
- );
-
- await user.click(screen.getByRole('link', { name: 'show_more' }));
-
- expect(await screen.findAllByText(/.*metric..+.name.*/)).toHaveLength(ALL_CONDITIONS);
- expect(await screen.findAllByText('quality_gates.operator', { exact: false })).toHaveLength(
- ALL_CONDITIONS,
- );
-});
-
-function renderQualityGateConditions(props: Partial<QualityGateConditionsProps> = {}) {
- const conditions: QualityGateStatusConditionEnhanced[] = [];
- for (let i = ALL_CONDITIONS; i > 0; --i) {
- conditions.push(
- mockQualityGateStatusConditionEnhanced({
- measure: mockMeasureEnhanced({ metric: mockMetric({ key: i.toString() }) }),
- }),
- );
- }
-
- return renderComponent(
- <QualityGateConditions component={mockComponent()} failedConditions={conditions} {...props} />,
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { screen } from '@testing-library/react';
-import React from 'react';
-import { mockQualityGateStatusConditionEnhanced } from '../../../../helpers/mocks/quality-gates';
-import { mockMetric } from '../../../../helpers/testMocks';
-import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-import { MetricKey, MetricType } from '../../../../types/metrics';
-import { QualityGateStatusConditionEnhanced } from '../../../../types/quality-gates';
-import QualityGateCondition from '../QualityGateCondition';
-import QualityGateSimplifiedCondition from '../QualityGateSimplifiedCondition';
-
-it('should show simplified condition', async () => {
- renderQualityGateCondition({
- condition: quickMock(MetricKey.new_violations, MetricType.Integer),
- });
- expect(await screen.findByText('metric.new_violations.name')).toBeInTheDocument();
-});
-
-function renderQualityGateCondition(props: Partial<QualityGateCondition['props']>) {
- return renderComponent(
- <QualityGateSimplifiedCondition
- component={{ key: 'abcd-key' }}
- condition={mockQualityGateStatusConditionEnhanced()}
- {...props}
- />,
- );
-}
-
-function quickMock(
- metric: MetricKey,
- type = MetricType.Rating,
- addPeriod = false,
- value = '3',
-): QualityGateStatusConditionEnhanced {
- return mockQualityGateStatusConditionEnhanced({
- error: '1',
- measure: {
- metric: mockMetric({
- key: metric,
- name: metric,
- type,
- }),
- value,
- ...(addPeriod ? { period: { value, index: 1 } } : {}),
- },
- metric,
- ...(addPeriod ? { period: 1 } : {}),
- });
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2024 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 { screen } from '@testing-library/react';
-import * as React from 'react';
-import { mockQualityGateStatusCondition } from '../../../../helpers/mocks/quality-gates';
-import { mockCurrentUser } from '../../../../helpers/testMocks';
-import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-import { MetricKey } from '../../../../types/metrics';
-import { SonarLintPromotion, SonarLintPromotionProps } from '../SonarLintPromotion';
-
-it('should render correctly', () => {
- renderSonarLintPromotion();
- expect(
- screen.queryByText('overview.fix_failed_conditions_with_sonarlint'),
- ).not.toBeInTheDocument();
-
- renderSonarLintPromotion({ currentUser: mockCurrentUser({ usingSonarLintConnectedMode: true }) });
- expect(
- screen.queryByText('overview.fix_failed_conditions_with_sonarlint'),
- ).not.toBeInTheDocument();
-});
-
-it.each(
- [
- MetricKey.new_blocker_violations,
- MetricKey.new_critical_violations,
- MetricKey.new_info_violations,
- MetricKey.new_violations,
- MetricKey.new_major_violations,
- MetricKey.new_minor_violations,
- MetricKey.new_code_smells,
- MetricKey.new_bugs,
- MetricKey.new_vulnerabilities,
- MetricKey.new_security_rating,
- MetricKey.new_maintainability_rating,
- MetricKey.new_reliability_rating,
- ].map(Array.of),
-)('should show message for %s', async (metric) => {
- renderSonarLintPromotion({
- qgConditions: [mockQualityGateStatusCondition({ metric: metric as MetricKey })],
- });
-
- expect(
- await screen.findByText('overview.fix_failed_conditions_with_sonarlint'),
- ).toBeInTheDocument();
-});
-
-function renderSonarLintPromotion(props: Partial<SonarLintPromotionProps> = {}) {
- return renderComponent(<SonarLintPromotion currentUser={mockCurrentUser()} {...props} />);
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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-2024 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 styled from '@emotion/styled';
+import { Badge, ButtonSecondary, themeBorder, themeColor } from 'design-system';
+import React from 'react';
+import { useIntl } from 'react-intl';
+import {
+ DEFAULT_ISSUES_QUERY,
+ isIssueMeasure,
+ propsToIssueParams,
+} from '../../../components/shared/utils';
+import { getBranchLikeQuery } from '../../../helpers/branch-like';
+import { getLocalizedMetricName, translate } 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 { 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),
+ );
+
+ return (
+ <ul className="sw-flex sw-items-center sw-gap-2 sw-flex-wrap sw-mb-4">
+ {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 (
+ <StyledConditionButton className="sw-px-3 sw-py-2 sw-rounded-1 sw-body-sm" to={url}>
+ <Badge className="sw-mr-2 sw-px-1" variant="deleted">
+ {translate('overview.measures.failed_badge')}
+ </Badge>
+ <SpanDanger>
+ <FailedMetric condition={condition} />
+ </SpanDanger>
+ </StyledConditionButton>
+ );
+}
+
+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,
+ actual,
+ measure: {
+ metric: { type, domain },
+ },
+ } = condition;
+ const intl = useIntl();
+
+ return (
+ <>
+ {intl.formatMessage(
+ { id: 'overview.failed_condition.x_rating_required' },
+ {
+ rating: `${intl.formatMessage({
+ id: `metric_domain.${domain}`,
+ })} ${intl.formatMessage({ id: 'metric.type.RATING' }).toLowerCase()}`,
+ value: <strong className="sw-body-sm-highlight">{formatMeasure(actual, type)}</strong>,
+ threshold: formatMeasure(error, type),
+ },
+ )}
+ </>
+ );
+}
+
+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: (
+ <>
+ {condition.op === 'GT' ? <>≤</> : <>≥</>}{' '}
+ {formatMeasure(error, getShortType(metric.type), measureFormattingOptions)}
+ </>
+ ),
+ },
+ )}
+ </>
+ );
+}
+
+function getQGConditionUrl(
+ componentKey: string,
+ condition: QualityGateStatusConditionEnhanced,
+ branchLike?: BranchLike,
+) {
+ const { metric } = condition;
+ const sinceLeakPeriod = isDiffMetric(metric);
+ const ratingIssueType = RATING_METRICS_MAPPING[metric];
+
+ if (ratingIssueType) {
+ if (ratingIssueType === IssueType.SecurityHotspot) {
+ return getComponentSecurityHotspotsUrl(componentKey, {
+ ...getBranchLikeQuery(branchLike),
+ ...(sinceLeakPeriod ? { sinceLeakPeriod: 'true' } : {}),
+ });
+ }
+ return getComponentIssuesUrl(componentKey, {
+ ...DEFAULT_ISSUES_QUERY,
+ types: ratingIssueType,
+ ...getBranchLikeQuery(branchLike),
+ ...(sinceLeakPeriod ? { sinceLeakPeriod: 'true' } : {}),
+ ...(ratingIssueType !== IssueType.CodeSmell
+ ? { severities: RATING_TO_SEVERITIES_MAPPING[Number(condition.error) - 1] }
+ : {}),
+ });
+ }
+
+ if (isIssueMeasure(condition.measure.metric.key)) {
+ return getComponentIssuesUrl(componentKey, {
+ ...propsToIssueParams(condition.measure.metric.key, condition.period != null),
+ ...getBranchLikeQuery(branchLike),
+ });
+ }
+
+ return getComponentDrilldownUrl({
+ componentKey,
+ metric,
+ branchLike,
+ listView: true,
+ });
+}
+
+const StyledConditionButton = styled(ButtonSecondary)`
+ --border: ${themeBorder('default')};
+`;
+
+const SpanDanger = styled.span`
+ color: ${themeColor('danger')};
+`;
import { PullRequest } from '../../../types/branch-like';
import { Component } from '../../../types/types';
import { AnalysisStatus } from '../components/AnalysisStatus';
-import BranchQualityGate from '../components/BranchQualityGate';
import IgnoredConditionWarning from '../components/IgnoredConditionWarning';
import ZeroNewIssuesSimplificationGuide from '../components/ZeroNewIssuesSimplificationGuide';
import '../styles.css';
import { PR_METRICS, Status } from '../utils';
+import BranchQualityGate from './BranchQualityGate';
import MeasuresCardPanel from './MeasuresCardPanel';
import PullRequestMetaTopBar from './PullRequestMetaTopBar';
import SonarLintAd from './SonarLintAd';
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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
+ const maintainabilityRatingLink = byRole('link', {
+ name: 'overview.measures.failed_badge overview.failed_condition.x_rating_requiredmetric_domain.Maintainability metric.type.rating E A',
+ }).get();
+ expect(maintainabilityRatingLink).toBeInTheDocument();
+ expect(maintainabilityRatingLink).toHaveAttribute(
+ 'href',
+ '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=CODE_SMELL&pullRequest=1001&sinceLeakPeriod=true&id=my-project',
+ );
+
+ // Security Hotspots rating condition
+ const securityHotspotsRatingLink = byRole('link', {
+ name: 'overview.measures.failed_badge overview.failed_condition.x_rating_requiredmetric_domain.Security Review metric.type.rating E A',
+ }).get();
+ expect(securityHotspotsRatingLink).toBeInTheDocument();
+ expect(securityHotspotsRatingLink).toHaveAttribute(
+ 'href',
+ '/security_hotspots?id=my-project&pullRequest=1001',
+ );
+
+ // New code smells
+ const codeSmellsLink = byRole('link', {
+ name: 'overview.measures.failed_badge overview.failed_condition.x_required 5 Code Smells≤ 1',
+ }).get();
+ expect(codeSmellsLink).toBeInTheDocument();
+ expect(codeSmellsLink).toHaveAttribute(
+ 'href',
+ '/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=CODE_SMELL&pullRequest=1001&id=my-project',
+ );
+
+ // Conditions to cover
+ const conditionToCoverLink = byRole('link', {
+ name: 'overview.measures.failed_badge overview.failed_condition.x_required 5 Conditions to cover≥ 10',
+ }).get();
+ expect(conditionToCoverLink).toBeInTheDocument();
+ expect(conditionToCoverLink).toHaveAttribute(
+ 'href',
+ '/component_measures?id=my-project&metric=conditions_to_cover&pullRequest=1001&view=list',
+ );
+
+ 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}
+ />,
+ );
+}