`;
DiscreetLink.displayName = 'DiscreetLink';
+export const ContentLink = styled(HoverLink)`
+ --color: ${themeColor('pageContent')};
+ --border: ${themeBorder('default', 'contentLinkBorder')};
+`;
+ContentLink.displayName = 'ContentLink';
+
export const StandoutLink = styled(StyledBaseLink)`
${tw`sw-font-semibold`}
${tw`sw-no-underline`}
import { theme as twTheme } from 'twin.macro';
import { BasePlacement, PopupPlacement } from '../helpers/positioning';
import { themeColor, themeContrast } from '../helpers/theme';
+import { QGStatus } from '../types/quality-gates';
const SIZE = {
sm: twTheme('spacing.4'),
xl: twTheme('spacing.16'),
};
-type QGStatus = 'ERROR' | 'OK' | 'NONE' | 'NOT_COMPUTED';
-
interface Props {
ariaLabel?: string;
className?: string;
import React from 'react';
import { MemoryRouter, Route, Routes, useLocation } from 'react-router-dom';
import { render } from '../../helpers/testUtils';
-import { DiscreetLink, StandoutLink as Link } from '../Link';
+import { ContentLink, DiscreetLink, StandoutLink as Link } from '../Link';
beforeAll(() => {
const { location } = window;
// This functionality won't be needed once we update the breadcrumbs
it('should remove focus after link is clicked', async () => {
const { user } = setupWithMemoryRouter(
- <Link blurAfterClick icon={<div>Icon</div>} to="/initial" />
+ <Link blurAfterClick icon={<div>Icon</div>} to="/initial" />,
);
await user.click(screen.getByRole('link'));
const { user } = setupWithMemoryRouter(
<button onClick={buttonOnClick} type="button">
<Link stopPropagation to="/second" />
- </button>
+ </button>,
);
await user.click(screen.getByRole('link'));
it('should call onClick when one is passed', async () => {
const onClick = jest.fn();
- const { user } = setupWithMemoryRouter(
- <Link onClick={onClick} stopPropagation to="/second" />
- );
+ const { user } = setupWithMemoryRouter(<Link onClick={onClick} stopPropagation to="/second" />);
await user.click(screen.getByRole('link'));
expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument();
});
-it('discreet links also can be external indicated by the OpenNewTabIcon', () => {
- setupWithMemoryRouter(<DiscreetLink to="https://google.com">external link</DiscreetLink>);
+it.each([
+ ['discreet', DiscreetLink],
+ ['content', ContentLink],
+])('%s links also can be external indicated by the OpenNewTabIcon', (_, LinkComponent) => {
+ setupWithMemoryRouter(<LinkComponent to="https://google.com">external link</LinkComponent>);
expect(screen.getByRole('link')).toBeVisible();
expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument();
/>
<Route element={<ShowPath />} path="/second" />
</Routes>
- </MemoryRouter>
+ </MemoryRouter>,
);
};
linkTooltipDefault: COLORS.indigo[200],
linkTooltipActive: COLORS.indigo[100],
linkBorder: COLORS.indigo[300],
+ contentLinkBorder: COLORS.blueGrey[200],
// discreet select
discreetBorder: secondary.default,
qgIndicatorFailed: COLORS.red[200],
qgIndicatorNotComputed: COLORS.blueGrey[200],
+ // quality gate status card
+ qgCardFailed: COLORS.red[300],
+
// quality gate texts colors
qgConditionNotCayc: COLORS.red[600],
qgConditionCayc: COLORS.green[600],
+ qgCardTitle: COLORS.blueGrey[700],
// main bar
mainBar: COLORS.white,
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+export type QGStatus = 'ERROR' | 'OK' | 'NONE' | 'NOT_COMPUTED';
};
}
-export function showLeakMeasure(branchLike?: BranchLike) {
- return isPullRequest(branchLike);
-}
-
function skipRootDir(breadcrumbs: ComponentMeasure[]) {
return breadcrumbs.filter((component) => {
return !(component.qualifier === ComponentQualifier.Directory && component.name === '/');
if (qualifier === ComponentQualifier.Application) {
return [...APPLICATION_METRICS];
}
- if (showLeakMeasure(branchLike)) {
+ if (isPullRequest(branchLike)) {
return [...LEAK_METRICS];
}
return [...METRICS];
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import styled from '@emotion/styled';
+import classNames from 'classnames';
+import { Card, ContentLink, PageContentFontWrapper, themeColor } from 'design-system';
+import * as React from 'react';
+import { To } from 'react-router-dom';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { localizeMetric } from '../../../helpers/measures';
+import { MetricKey } from '../../../types/metrics';
+
+interface Props {
+ url: To;
+ value: string;
+ metric: MetricKey;
+ label: string;
+ failed?: boolean;
+ icon?: React.ReactElement;
+}
+
+export default function MeasuresCard(
+ props: React.PropsWithChildren<Props & React.HTMLAttributes<HTMLDivElement>>,
+) {
+ const { failed, children, metric, icon, value, url, label, ...rest } = props;
+
+ return (
+ <StyledCard
+ className={classNames(
+ 'sw-h-fit sw-p-8 sw-rounded-2 sw-flex sw-justify-between sw-items-center sw-text-base',
+ {
+ failed,
+ },
+ )}
+ {...rest}
+ >
+ <PageContentFontWrapper className="sw-flex sw-flex-col sw-gap-1 sw-justify-between">
+ <StyledTitleContainer className="sw-flex sw-items-center sw-gap-2 sw-font-semibold">
+ {value ? (
+ <ContentLink
+ aria-label={translateWithParameters(
+ 'overview.see_more_details_on_x_of_y',
+ value,
+ localizeMetric(metric),
+ )}
+ className="it__overview-measures-value sw-text-lg"
+ to={url}
+ >
+ {value}
+ </ContentLink>
+ ) : (
+ <span> — </span>
+ )}
+ {translate(label)}
+ </StyledTitleContainer>
+ {children && <div className="sw-flex sw-flex-col">{children}</div>}
+ </PageContentFontWrapper>
+
+ {icon && <div>{icon}</div>}
+ </StyledCard>
+ );
+}
+
+export const StyledCard = styled(Card)`
+ &.failed {
+ border-color: ${themeColor('qgCardFailed')};
+ }
+`;
+
+const StyledTitleContainer = styled.div`
+ color: ${themeColor('qgCardTitle')};
+`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { TextError } from 'design-system';
+import * as React from 'react';
+import { To } from 'react-router-dom';
+import { formatMeasure } from '../../../helpers/measures';
+import { MetricKey, MetricType } from '../../../types/metrics';
+import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
+import MeasuresCard from './MeasuresCard';
+
+interface Props {
+ failedConditions: QualityGateStatusConditionEnhanced[];
+ label: string;
+ url: To;
+ value: string;
+ failingConditionMetric: MetricKey;
+ requireLabel: string;
+}
+
+export default function MeasuresCardNumber(
+ props: React.PropsWithChildren<Props & React.HTMLAttributes<HTMLDivElement>>,
+) {
+ const { label, value, failedConditions, url, failingConditionMetric, requireLabel, ...rest } =
+ props;
+
+ const failed = Boolean(
+ failedConditions.find((condition) => condition.metric === failingConditionMetric),
+ );
+
+ return (
+ <MeasuresCard
+ url={url}
+ value={formatMeasure(value, MetricType.ShortInteger)}
+ metric={failingConditionMetric}
+ label={label}
+ failed={failed}
+ {...rest}
+ >
+ {failed && <TextError className="sw-font-regular sw-mt-2" text={requireLabel} />}
+ </MeasuresCard>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import classNames from 'classnames';
+import * as React from 'react';
+import { useIntl } from 'react-intl';
+import { getLeakValue } from '../../../components/measure/utils';
+import { getBranchLikeQuery } from '../../../helpers/branch-like';
+import { findMeasure } from '../../../helpers/measures';
+import {
+ getComponentDrilldownUrl,
+ getComponentIssuesUrl,
+ getComponentSecurityHotspotsUrl,
+} from '../../../helpers/urls';
+import { BranchLike } from '../../../types/branch-like';
+import { MetricKey } from '../../../types/metrics';
+import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
+import { Component, MeasureEnhanced } from '../../../types/types';
+import { MeasurementType, getMeasurementMetricKey } from '../utils';
+import MeasuresCardNumber from './MeasuresCardNumber';
+import MeasuresCardPercent from './MeasuresCardPercent';
+
+interface Props {
+ className?: string;
+ branchLike?: BranchLike;
+ component: Component;
+ measures: MeasureEnhanced[];
+ failedConditions: QualityGateStatusConditionEnhanced[];
+}
+
+export default function MeasuresCardPanel(props: React.PropsWithChildren<Props>) {
+ const { branchLike, component, measures, failedConditions, className } = props;
+
+ const intl = useIntl();
+
+ const newViolations = getLeakValue(findMeasure(measures, MetricKey.new_violations)) as string;
+ const newSecurityHotspots = getLeakValue(
+ findMeasure(measures, MetricKey.new_security_hotspots),
+ ) as string;
+
+ return (
+ <div className={classNames('sw-grid sw-grid-cols-2 sw-gap-4 sw-mt-4', className)}>
+ <MeasuresCardNumber
+ data-test="overview__measures-new-violations"
+ label={newViolations === '1' ? 'issue' : 'issues'}
+ url={getComponentIssuesUrl(component.key, {
+ ...getBranchLikeQuery(branchLike),
+ resolved: 'false',
+ })}
+ value={newViolations}
+ failedConditions={failedConditions}
+ failingConditionMetric={MetricKey.new_violations}
+ requireLabel={intl.formatMessage(
+ { id: 'overview.quality_gate.require_fixing' },
+ {
+ count: newViolations,
+ },
+ )}
+ />
+
+ <MeasuresCardNumber
+ label={
+ newSecurityHotspots === '1'
+ ? 'issue.type.SECURITY_HOTSPOT'
+ : 'issue.type.SECURITY_HOTSPOT.plural'
+ }
+ url={getComponentSecurityHotspotsUrl(component.key, {
+ ...getBranchLikeQuery(branchLike),
+ resolved: 'false',
+ })}
+ value={newSecurityHotspots}
+ failedConditions={failedConditions}
+ failingConditionMetric={MetricKey.new_security_hotspots_reviewed}
+ requireLabel={intl.formatMessage(
+ { id: 'overview.quality_gate.require_reviewing' },
+ {
+ count: newSecurityHotspots,
+ },
+ )}
+ />
+
+ <MeasuresCardPercent
+ componentKey={component.key}
+ branchLike={branchLike}
+ measurementType={MeasurementType.Coverage}
+ label="overview.quality_gate.coverage"
+ url={getComponentDrilldownUrl({
+ componentKey: component.key,
+ metric: getMeasurementMetricKey(MeasurementType.Coverage, true),
+ branchLike,
+ listView: true,
+ })}
+ failedConditions={failedConditions}
+ failingConditionMetric={MetricKey.new_coverage}
+ newLinesMetric={MetricKey.new_lines_to_cover}
+ afterMergeMetric={MetricKey.coverage}
+ measures={measures}
+ />
+
+ <MeasuresCardPercent
+ componentKey={component.key}
+ branchLike={branchLike}
+ measurementType={MeasurementType.Duplication}
+ label="overview.quality_gate.duplications"
+ url={getComponentDrilldownUrl({
+ componentKey: component.key,
+ metric: getMeasurementMetricKey(MeasurementType.Duplication, true),
+ branchLike,
+ listView: true,
+ })}
+ failedConditions={failedConditions}
+ failingConditionMetric={MetricKey.new_duplicated_lines_density}
+ newLinesMetric={MetricKey.new_lines}
+ afterMergeMetric={MetricKey.duplicated_lines_density}
+ measures={measures}
+ />
+ </div>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import {
+ ContentLink,
+ CoverageIndicator,
+ DuplicationsIndicator,
+ LightLabel,
+ TextError,
+} from 'design-system';
+import * as React from 'react';
+import { FormattedMessage, useIntl } from 'react-intl';
+import { To } from 'react-router-dom';
+import { duplicationRatingConverter, getLeakValue } from '../../../components/measure/utils';
+import { translate, 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 { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
+import { MeasureEnhanced } from '../../../types/types';
+import { MeasurementType, getMeasurementMetricKey } from '../utils';
+import MeasuresCard from './MeasuresCard';
+
+interface Props {
+ componentKey: string;
+ branchLike?: BranchLike;
+ measurementType: MeasurementType;
+ label: string;
+ url: To;
+ measures: MeasureEnhanced[];
+ failedConditions: QualityGateStatusConditionEnhanced[];
+ failingConditionMetric: MetricKey;
+ newLinesMetric: MetricKey;
+ afterMergeMetric: MetricKey;
+}
+
+export default function MeasuresCardPercent(
+ props: React.PropsWithChildren<Props & React.HTMLAttributes<HTMLDivElement>>,
+) {
+ const {
+ componentKey,
+ branchLike,
+ measurementType,
+ label,
+ url,
+ measures,
+ failedConditions,
+ failingConditionMetric,
+ newLinesMetric,
+ afterMergeMetric,
+ } = props;
+
+ const intl = useIntl();
+
+ const metricKey = getMeasurementMetricKey(measurementType, true);
+
+ const value = getLeakValue(findMeasure(measures, metricKey));
+
+ const newLinesValue = getLeakValue(findMeasure(measures, newLinesMetric));
+ const newLinesLabel =
+ measurementType === MeasurementType.Coverage
+ ? 'overview.quality_gate.on_x_new_lines_to_cover'
+ : 'overview.quality_gate.on_x_new_lines';
+ const newLinesUrl = getComponentDrilldownUrl({
+ componentKey,
+ metric: newLinesMetric,
+ branchLike,
+ listView: true,
+ });
+
+ const afterMergeValue = findMeasure(measures, afterMergeMetric)?.value;
+
+ const failedCondition = failedConditions.find(
+ (condition) => condition.metric === failingConditionMetric,
+ );
+
+ let errorRequireLabel = '';
+ if (failedCondition) {
+ errorRequireLabel = intl.formatMessage(
+ { id: 'overview.quality_gate.required_x' },
+ {
+ operator: failedCondition.op === 'GT' ? '<=' : '>=',
+ value: formatMeasure(
+ failedCondition.level === 'ERROR' ? failedCondition.error : failedCondition.warning,
+ MetricType.Percent,
+ {
+ decimals: 2,
+ omitExtraDecimalZeros: true,
+ },
+ ),
+ },
+ );
+ }
+
+ return (
+ <MeasuresCard
+ value={formatMeasure(value, MetricType.Percent)}
+ metric={metricKey}
+ url={url}
+ label={label}
+ failed={Boolean(failedCondition)}
+ icon={renderIcon(measurementType, value)}
+ >
+ <div className="sw-flex sw-flex-col">
+ <LightLabel className="sw-flex sw-items-center sw-gap-1">
+ <FormattedMessage
+ defaultMessage={translate(newLinesLabel)}
+ id={newLinesLabel}
+ values={{
+ link: (
+ <ContentLink
+ aria-label={translateWithParameters(
+ 'overview.see_more_details_on_x_y',
+ newLinesValue ?? '0',
+ localizeMetric(newLinesMetric),
+ )}
+ className="sw-body-md-highlight sw-text-lg"
+ to={newLinesUrl}
+ >
+ {formatMeasure(newLinesValue ?? '0', MetricType.ShortInteger)}
+ </ContentLink>
+ ),
+ }}
+ />
+ </LightLabel>
+
+ {afterMergeValue && (
+ <LightLabel className="sw-mt-2">
+ <FormattedMessage
+ defaultMessage={translate('overview.quality_gate.x_estimated_after_merge')}
+ id="overview.quality_gate.x_estimated_after_merge"
+ values={{
+ value: <strong>{formatMeasure(afterMergeValue, MetricType.Percent)}</strong>,
+ }}
+ />
+ </LightLabel>
+ )}
+
+ {failedCondition && (
+ <TextError className="sw-mt-2 sw-font-regular" text={errorRequireLabel} />
+ )}
+ </div>
+ </MeasuresCard>
+ );
+}
+
+function renderIcon(type: MeasurementType, value?: string) {
+ if (type === MeasurementType.Coverage) {
+ return <CoverageIndicator value={value} size="md" />;
+ }
+
+ const rating = duplicationRatingConverter(Number(value));
+ return <DuplicationsIndicator rating={rating} size="md" />;
+}
} from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like';
import { IssueType } from '../../../types/issues';
-import { MetricKey, MetricType } from '../../../types/metrics';
+import { MetricType } from '../../../types/metrics';
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
import { Component } from '../../../types/types';
import {
const { branchLike, component, failedConditions } = props;
const filteredFailedConditions = failedConditions.filter(
- (condition) => !METRICS_REPORTED_IN_OVERVIEW_CARDS.includes(condition.metric as MetricKey),
+ (condition) => !METRICS_REPORTED_IN_OVERVIEW_CARDS.includes(condition.metric),
);
return (
].map(Array.of),
)('should show message for %s', async (metric) => {
renderSonarLintPromotion({
- qgConditions: [mockQualityGateStatusCondition({ metric: metric as string })],
+ qgConditions: [mockQualityGateStatusCondition({ metric: metric as MetricKey })],
});
expect(
* 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,
- Card,
- CenteredLayout,
- CoverageIndicator,
- DuplicationsIndicator,
- Spinner,
-} from 'design-system';
+import { BasicSeparator, CenteredLayout, Spinner } from 'design-system';
import { uniq } from 'lodash';
import * as React from 'react';
import { useEffect, useState } from 'react';
import { getMeasuresWithMetrics } from '../../../api/measures';
-import { duplicationRatingConverter } from '../../../components/measure/utils';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { enhanceConditionWithMeasure, enhanceMeasuresWithMetrics } from '../../../helpers/measures';
import { isDefined } from '../../../helpers/types';
import { useBranchStatusQuery } from '../../../queries/branch';
import { PullRequest } from '../../../types/branch-like';
-import { IssueType } from '../../../types/issues';
import { Component, MeasureEnhanced } from '../../../types/types';
-import MeasuresPanelIssueMeasure from '../branches/MeasuresPanelIssueMeasure';
-import MeasuresPanelPercentMeasure from '../branches/MeasuresPanelPercentMeasure';
+import MeasuresCardPanel from '../branches/MeasuresCardPanel';
import BranchQualityGate from '../components/BranchQualityGate';
import IgnoredConditionWarning from '../components/IgnoredConditionWarning';
import MetaTopBar from '../components/MetaTopBar';
import SonarLintPromotion from '../components/SonarLintPromotion';
import '../styles.css';
-import { MeasurementType, PR_METRICS, Status } from '../utils';
+import { PR_METRICS, Status } from '../utils';
interface Props {
branchLike: PullRequest;
/>
)}
- <div className="sw-flex-1">
- <div className="sw-grid sw-grid-cols-2 sw-gap-4 sw-mt-4">
- {[
- IssueType.Bug,
- IssueType.CodeSmell,
- IssueType.Vulnerability,
- IssueType.SecurityHotspot,
- ].map((type: IssueType) => (
- <Card key={type} className="sw-p-8">
- <MeasuresPanelIssueMeasure
- branchLike={branchLike}
- component={component}
- isNewCodeTab
- measures={measures}
- type={type}
- />
- </Card>
- ))}
+ <MeasuresCardPanel
+ className="sw-flex-1"
+ branchLike={branchLike}
+ component={component}
+ failedConditions={failedConditions}
+ measures={measures}
+ />
- {[MeasurementType.Coverage, MeasurementType.Duplication].map(
- (type: MeasurementType) => (
- <Card key={type} className="sw-p-8">
- <MeasuresPanelPercentMeasure
- branchLike={branchLike}
- component={component}
- measures={measures}
- ratingIcon={renderMeasureIcon(type)}
- type={type}
- useDiffMetric
- />
- </Card>
- ),
- )}
- </div>
- </div>
<SonarLintPromotion qgConditions={conditions} />
</div>
</div>
</CenteredLayout>
);
}
-
-function renderMeasureIcon(type: MeasurementType) {
- if (type === MeasurementType.Coverage) {
- return function CoverageIndicatorRenderer(value?: string) {
- return <CoverageIndicator value={value} size="md" />;
- };
- }
-
- return function renderDuplicationIcon(value?: string) {
- const rating = duplicationRatingConverter(Number(value));
-
- return <DuplicationsIndicator rating={rating} size="md" />;
- };
-}
MetricKey.new_coverage,
MetricKey.new_lines_to_cover,
+ MetricKey.new_violations,
MetricKey.duplicated_lines_density,
MetricKey.new_duplicated_lines_density,
MetricKey.new_lines,
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { MetricKey } from '../../types/metrics';
import {
QualityGateApplicationStatus,
QualityGateProjectStatus,
actual: '10',
error: '0',
level: 'ERROR',
- metric: 'foo',
+ metric: MetricKey.bugs,
op: 'GT',
...overrides,
};
actual: '10',
error: '0',
level: 'ERROR',
- metric: 'foo',
+ metric: MetricKey.bugs,
op: 'GT',
measure: mockMeasureEnhanced({ ...(overrides.measure || {}) }),
...overrides,
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { MetricKey } from '../types/metrics';
import {
QualityGateApplicationStatusChildProject,
QualityGateProjectStatus,
actual: c.actualValue,
error: c.errorThreshold,
level: c.status,
- metric: c.metricKey,
+ metric: c.metricKey as MetricKey,
op: c.comparator,
period: c.periodIndex,
}))
actual: c.value,
error: c.errorThreshold,
level: c.status,
- metric: c.metric,
+ metric: c.metric as MetricKey,
op: c.comparator,
period: c.periodIndex,
}))
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { BranchLike } from './branch-like';
+import { MetricKey } from './metrics';
import { CaycStatus, MeasureEnhanced, Metric, Status } from './types';
import { UserBase } from './users';
actual?: string;
error?: string;
level: Status;
- metric: string;
+ metric: MetricKey;
op: string;
period?: number;
warning?: string;
overview.quality_gate.application.non_cayc.projects_x={0} project(s) in this application use a Quality Gate that does not comply with Clean as You Code
overview.quality_gate.show_project_conditions_x=Show failed conditions for project {0}
overview.quality_gate.hide_project_conditions_x=Hide failed conditions for project {0}
+overview.quality_gate.coverage=Coverage
+overview.quality_gate.duplications=Duplications
+overview.quality_gate.on_x_new_lines_to_cover=On {link} New Lines to cover
+overview.quality_gate.on_x_new_lines=On {link} New Lines
+overview.quality_gate.x_estimated_after_merge={value} Estimated after merge
+overview.quality_gate.require_fixing={count, plural, one {requires} other {require}} fixing
+overview.quality_gate.require_reviewing={count, plural, one {requires} other {require}} reviewing
+overview.quality_gate.required_x=required {operator} {value}
overview.quality_profiles=Quality Profiles used
overview.new_code_period_x=New Code: {0}
overview.max_new_code_period_from_x=Max New Code from: {0}