);
}
-export function TextError({ text, className }: { className?: string; text: string }) {
- return (
- <StyledTextError className={className} title={text}>
- {text}
- </StyledTextError>
- );
+export function TextError({
+ text,
+ className,
+}: Readonly<{
+ className?: string;
+ text: string | React.ReactNode;
+}>) {
+ if (typeof text === 'string') {
+ return (
+ <StyledTextError className={className} title={text}>
+ {text}
+ </StyledTextError>
+ );
+ }
+ return <StyledTextError className={className}>{text}</StyledTextError>;
}
export function TextSuccess({ text, className }: Readonly<{ className?: string; text: string }>) {
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { useTheme } from '@emotion/react';
+import { Fragment } from 'react';
import { themeColor, themeContrast } from '../../helpers/theme';
import { CustomIcon, IconProps } from './Icon';
-export function HelperHintIcon(iconProps: IconProps) {
+type Props = IconProps & {
+ raised?: boolean;
+};
+
+export function HelperHintIcon({ raised, ...iconProps }: Props) {
const theme = useTheme();
return (
<CustomIcon {...iconProps}>
- <circle cx="8" cy="8" fill={themeColor('iconHelperHint')({ theme })} r="7" />
- <path
- d="M6.82812 10.2301h1.61506v-.1449c.00852-.83094.30682-1.21872.98012-1.62355.7969-.47301 1.3168-1.09943 1.3168-2.10085C10.7401 4.86932 9.53835 4 7.84659 4 6.29972 4 5.03835 4.80966 5 6.5142h1.73864c.02556-.6946.54119-1.06534 1.09943-1.06534.57528 0 1.03977.38353 1.03977.97586 0 .55823-.40483.92897-.92898 1.26136-.71591.4517-1.11647.90767-1.12074 2.39912v.1449Zm.83949 2.7273c.54546 0 1.01847-.456 1.02273-1.0227-.00426-.5583-.47727-1.0142-1.02273-1.0142-.5625 0-1.02698.4559-1.02272 1.0142-.00426.5667.46022 1.0227 1.02272 1.0227Z"
- fill={themeContrast('iconHelperHint')({ theme })}
- />
+ {raised ? (
+ // eslint-disable-next-line react/jsx-fragments
+ <Fragment>
+ <circle cx="8" cy="8" fill={themeColor('iconHelperHintRaised')({ theme })} r="7" />
+ <path
+ d="M6.82812 10.2301H8.44318V10.0852C8.4517 9.25426 8.75 8.86648 9.4233 8.46165C10.2202 7.98864 10.7401 7.36222 10.7401 6.3608C10.7401 4.86932 9.53835 4 7.84659 4C6.29972 4 5.03835 4.80966 5 6.5142H6.73864C6.7642 5.8196 7.27983 5.44886 7.83807 5.44886C8.41335 5.44886 8.87784 5.83239 8.87784 6.42472C8.87784 6.98295 8.47301 7.35369 7.94886 7.68608C7.23295 8.13778 6.83239 8.59375 6.82812 10.0852V10.2301ZM7.66761 12.9574C8.21307 12.9574 8.68608 12.5014 8.69034 11.9347C8.68608 11.3764 8.21307 10.9205 7.66761 10.9205C7.10511 10.9205 6.64063 11.3764 6.64489 11.9347C6.64063 12.5014 7.10511 12.9574 7.66761 12.9574Z"
+ fill={themeContrast('iconHelperHintRaised')({ theme })}
+ />
+ </Fragment>
+ ) : (
+ // eslint-disable-next-line react/jsx-fragments
+ <Fragment>
+ <circle cx="8" cy="8" fill={themeColor('iconHelperHint')({ theme })} r="7" />
+ <path
+ d="M6.82812 10.2301h1.61506v-.1449c.00852-.83094.30682-1.21872.98012-1.62355.7969-.47301 1.3168-1.09943 1.3168-2.10085C10.7401 4.86932 9.53835 4 7.84659 4 6.29972 4 5.03835 4.80966 5 6.5142h1.73864c.02556-.6946.54119-1.06534 1.09943-1.06534.57528 0 1.03977.38353 1.03977.97586 0 .55823-.40483.92897-.92898 1.26136-.71591.4517-1.11647.90767-1.12074 2.39912v.1449Zm.83949 2.7273c.54546 0 1.01847-.456 1.02273-1.0227-.00426-.5583-.47727-1.0142-1.02273-1.0142-.5625 0-1.02698.4559-1.02272 1.0142-.00426.5667.46022 1.0227 1.02272 1.0227Z"
+ fill={themeContrast('iconHelperHint')({ theme })}
+ />
+ </Fragment>
+ )}
</CustomIcon>
);
}
fill?: ThemeColors | CSSColor;
height?: number;
transform?: string;
+ viewBox?: string;
width?: number;
}
--- /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 { useTheme } from '@emotion/react';
+import { themeColor, themeContrast } from '../../helpers';
+import { CustomIcon, IconProps } from './Icon';
+
+export function SnoozeCircleIcon(props: Readonly<{ neutral?: boolean } & IconProps>) {
+ const theme = useTheme();
+
+ const bgColor = themeColor('overviewCardWarningIcon')({ theme });
+ const iconColor = themeContrast('overviewCardWarningIcon')({ theme });
+
+ return (
+ <CustomIcon height="36" viewBox="0 0 36 36" width="36" {...props}>
+ <circle cx="18" cy="18" fill={bgColor} r="18" />
+ <path
+ d="M16.5319 17.2149H18.4624L15.7318 20.2936C15.3281 20.7536 15.6658 21.4613 16.2897 21.4613H19.4681C19.8718 21.4613 20.2021 21.1428 20.2021 20.7536C20.2021 20.3643 19.8718 20.0458 19.4681 20.0458H17.5376L20.2682 16.9672C20.6719 16.5072 20.3342 15.7994 19.7103 15.7994H16.5319C16.1282 15.7994 15.7979 16.1179 15.7979 16.5072C15.7979 16.8964 16.1282 17.2149 16.5319 17.2149ZM24.8265 13.9735C24.5696 14.2707 24.1071 14.3132 23.7915 14.0655L21.538 12.2537C21.2297 11.9989 21.1857 11.553 21.4499 11.2558C21.7069 10.9585 22.1693 10.9161 22.4849 11.1638L24.7384 12.9756C25.0467 13.2304 25.0907 13.6762 24.8265 13.9735ZM11.1735 13.9735C11.4304 14.2778 11.8929 14.3132 12.2012 14.0655L14.4546 12.2537C14.7703 11.9989 14.8143 11.553 14.5501 11.2558C14.2931 10.9514 13.8307 10.9161 13.5224 11.1638L11.2616 12.9756C10.9533 13.2304 10.9093 13.6762 11.1735 13.9735ZM18 13.6762C20.8334 13.6762 23.1382 15.8985 23.1382 18.6304C23.1382 21.3622 20.8334 23.5845 18 23.5845C15.1666 23.5845 12.8618 21.3622 12.8618 18.6304C12.8618 15.8985 15.1666 13.6762 18 13.6762ZM18 12.2608C14.3519 12.2608 11.3937 15.1129 11.3937 18.6304C11.3937 22.1478 14.3519 25 18 25C21.6481 25 24.6063 22.1478 24.6063 18.6304C24.6063 15.1129 21.6481 12.2608 18 12.2608Z"
+ fill={iconColor}
+ />
+ </CustomIcon>
+ );
+}
--- /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 { useTheme } from '@emotion/react';
+import { themeColor, themeContrast } from '../../helpers';
+import { CustomIcon, IconProps } from './Icon';
+
+export function TrendDownCircleIcon(props: Readonly<IconProps>) {
+ const theme = useTheme();
+
+ const bgColor = themeColor('overviewCardSuccessIcon')({ theme });
+ const iconColor = themeContrast('overviewCardSuccessIcon')({ theme });
+
+ return (
+ <CustomIcon height="36" viewBox="0 0 36 36" width="36" {...props}>
+ <circle cx="18" cy="16" fill={bgColor} r="16" />
+ <path
+ d="M23.3203 20.3404C23.3658 20.3291 23.4095 20.3116 23.4503 20.2886C23.4845 20.2687 23.5158 20.2441 23.5433 20.2156C23.567 20.1872 23.5886 20.157 23.6078 20.1254C23.6375 20.0862 23.6614 20.0428 23.6785 19.9967L23.6922 19.9051C23.7046 19.8587 23.7097 19.8107 23.7072 19.7627L23.6579 19.6389C23.6794 19.5866 23.6912 19.5308 23.6927 19.4743L22.4586 16.3778C22.3931 16.2136 22.2651 16.0821 22.1026 16.0122C21.9402 15.9424 21.7567 15.9399 21.5924 16.0054C21.4282 16.0709 21.2967 16.1989 21.2268 16.3613C21.157 16.5238 21.1545 16.7073 21.22 16.8715L21.9185 18.6241L18.0143 17.3095L18.5396 13.9999C18.5644 13.8431 18.5326 13.6826 18.4497 13.5473C18.3668 13.4119 18.2383 13.3106 18.0874 13.2615L13.1375 11.6461C13.0541 11.6189 12.9662 11.6084 12.8788 11.6152C12.7914 11.622 12.7062 11.646 12.628 11.6858C12.5499 11.7256 12.4804 11.7805 12.4235 11.8472C12.3666 11.9139 12.3234 11.9912 12.2964 12.0746C12.2485 12.2228 12.254 12.3831 12.3119 12.5277C12.348 12.6188 12.4038 12.7007 12.4751 12.7678C12.5465 12.8348 12.6318 12.8853 12.7249 12.9157L17.1303 14.3534L16.599 17.6297C16.5746 17.7848 16.6057 17.9435 16.6868 18.0779C16.7679 18.2123 16.8939 18.3137 17.0425 18.3643L21.1545 19.7683L19.7302 20.336C19.5659 20.4015 19.4344 20.5295 19.3646 20.6919C19.2947 20.8544 19.2923 21.0379 19.3577 21.2021C19.4232 21.3664 19.5512 21.4979 19.7137 21.5677C19.8761 21.6376 20.0596 21.64 20.2238 21.5746L23.3203 20.3404Z"
+ fill={iconColor}
+ />
+ </CustomIcon>
+ );
+}
--- /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 { useTheme } from '@emotion/react';
+import { themeColor, themeContrast } from '../../helpers';
+import { CustomIcon, IconProps } from './Icon';
+
+export function TrendUpCircleIcon(props: Readonly<IconProps>) {
+ const theme = useTheme();
+
+ const bgColor = themeColor('overviewCardErrorIcon')({ theme });
+ const iconColor = themeContrast('overviewCardErrorIcon')({ theme });
+
+ return (
+ <CustomIcon height="36" viewBox="0 0 36 36" width="36" {...props}>
+ <circle cx="18" cy="18" fill={bgColor} r="18" />
+ <g clipPath="url(#clip0_2971_11471)">
+ <path
+ d="M20.8955 12.253C20.7186 12.1492 20.5107 12.1169 20.3175 12.1633C20.1242 12.2096 19.9615 12.3308 19.8652 12.5001C19.7688 12.6695 19.7467 12.8732 19.8036 13.0663C19.8605 13.2595 19.9919 13.4263 20.1688 13.5301L21.7029 14.4305L16.8273 15.1807C16.6524 15.2084 16.4961 15.2967 16.386 15.43C16.276 15.5633 16.2193 15.7331 16.2258 15.9094L16.4151 19.6577L11.2365 20.409C11.1385 20.4231 11.0453 20.4562 10.9621 20.5065C10.879 20.5568 10.8076 20.6233 10.752 20.7022C10.6963 20.7811 10.6576 20.8708 10.6379 20.9662C10.6183 21.0617 10.6181 21.161 10.6374 21.2584C10.6567 21.3559 10.6952 21.4496 10.7505 21.5343C10.8059 21.619 10.877 21.6929 10.96 21.7518C11.0429 21.8108 11.136 21.8537 11.2339 21.8779C11.3318 21.9022 11.4327 21.9075 11.5306 21.8934L17.3493 21.0493C17.5267 21.0238 17.6858 20.9361 17.798 20.802C17.9101 20.668 17.968 20.4964 17.9612 20.3181L17.8022 16.5791L22.3971 15.8686L21.3833 17.6502C21.287 17.8195 21.2648 18.0232 21.3218 18.2163C21.3787 18.4095 21.5101 18.5763 21.687 18.6802C21.8639 18.784 22.0718 18.8163 22.265 18.7699C22.4583 18.7236 22.6209 18.6024 22.7173 18.4331L24.5341 15.2403C24.5768 15.1605 24.6052 15.0735 24.6182 14.983L24.6157 14.8623C24.6171 14.8187 24.6153 14.7749 24.6102 14.7313C24.5971 14.6769 24.576 14.6244 24.5477 14.5753C24.5303 14.5366 24.51 14.4991 24.487 14.4631C24.4598 14.4263 24.4278 14.3931 24.3918 14.3646C24.3477 14.3042 24.293 14.2519 24.2305 14.2103L20.8955 12.253Z"
+ fill={iconColor}
+ />
+ </g>
+ <defs>
+ <clipPath id="clip0_2971_11471">
+ <rect fill="white" height="18" transform="translate(9 9)" width="18" />
+ </clipPath>
+ </defs>
+ </CustomIcon>
+ );
+}
export { SeverityInfoIcon } from './SeverityInfoIcon';
export { SeverityMajorIcon } from './SeverityMajorIcon';
export { SeverityMinorIcon } from './SeverityMinorIcon';
+export { SnoozeCircleIcon } from './SnoozeCircleIcon';
export { SoftwareImpactSeverityHighIcon } from './SoftwareImpactSeverityHighIcon';
export { SoftwareImpactSeverityLowIcon } from './SoftwareImpactSeverityLowIcon';
export { SoftwareImpactSeverityMediumIcon } from './SoftwareImpactSeverityMediumIcon';
export { StatusResolvedIcon } from './StatusResolvedIcon';
export { TestFileIcon } from './TestFileIcon';
export { TrashIcon } from './TrashIcon';
+export { TrendDownCircleIcon } from './TrendDownCircleIcon';
export { TrendDirection, TrendIcon, TrendType } from './TrendIcon';
+export { TrendUpCircleIcon } from './TrendUpCircleIcon';
export { TriangleDownIcon } from './TriangleDownIcon';
export { TriangleLeftIcon } from './TriangleLeftIcon';
export { TriangleRightIcon } from './TriangleRightIcon';
iconStatusResolved: secondary.dark,
iconNotificationsOn: COLORS.indigo[300],
iconHelperHint: COLORS.blueGrey[100],
+ iconHelperHintRaised: COLORS.blueGrey[400],
iconRuleInheritanceOverride: danger.light,
// numbered list
// overview
iconOverviewIssue: COLORS.blueGrey[400],
+ overviewCardWarningIcon: COLORS.yellow[50],
+ overviewCardErrorIcon: COLORS.red[100],
+ overviewCardSuccessIcon: COLORS.green[200],
// graph - chart
graphPointCircleColor: COLORS.white,
pillInfoIcon: COLORS.blue[700],
pillAccent: COLORS.indigo[500],
+ // project cards
+ overviewCardWarningIcon: COLORS.yellow[700],
+ overviewCardErrorIcon: COLORS.red[500],
+ overviewCardSuccessIcon: COLORS.green[500],
+
// breadcrumbs
breadcrumb: secondary.dark,
iconSeverityInfo: COLORS.white,
iconStatusResolved: COLORS.white,
iconHelperHint: secondary.darker,
+ iconHelperHintRaised: COLORS.white,
// numbered list
numberedList: COLORS.indigo[800],
// TODO: Remove this mock (SONAR-21259)
const mockedMetrics = metrics.filter(
(metric) =>
- metric !== MetricKey.pullrequest_addressed_issues && metric !== MetricKey.new_accepted_issues,
+ metric !== MetricKey.pull_request_fixed_issues && metric !== MetricKey.new_accepted_issues,
);
const result = (await getJSON(COMPONENT_URL, {
additionalFields: 'metrics',
metricKeys: mockedMetrics.join(','),
...branchParameters,
}).catch(throwGlobalError)) as MeasuresAndMetaWithMetrics;
- if (metrics.includes(MetricKey.pullrequest_addressed_issues)) {
+ if (metrics.includes(MetricKey.pull_request_fixed_issues)) {
result.metrics.push({
- key: MetricKey.pullrequest_addressed_issues,
+ key: MetricKey.pull_request_fixed_issues,
name: 'Addressed Issues',
description: 'Addressed Issues',
domain: 'Reliability',
bestValue: '0',
});
result.component.measures?.push({
- metric: MetricKey.pullrequest_addressed_issues,
+ metric: MetricKey.pull_request_fixed_issues,
period: {
index: 0,
value: '11',
[
{
"metric": {
- "id": "3",
"key": "new_bugs",
- "name": "new_bugs",
+ "name": "New bugs",
"type": "INT",
},
},
{
"metric": {
- "id": "2",
"key": "new_reliability_remediation_effort",
- "name": "bugs",
+ "name": "Bugs",
"type": "INT",
},
},
"overall_category",
{
"metric": {
- "id": "4",
"key": "bugs",
- "name": "bugs",
+ "name": "Bugs",
"type": "INT",
},
},
{
"metric": {
- "id": "1",
"key": "reliability_remediation_effort",
- "name": "new_bugs",
+ "name": "New bugs",
"type": "INT",
},
},
*/
import { ComponentQualifier } from '../../../types/component';
import { MeasurePageView } from '../../../types/measures';
-import { MetricKey } from '../../../types/metrics';
+import { MetricKey, MetricType } from '../../../types/metrics';
import { ComponentMeasure } from '../../../types/types';
import * as utils from '../utils';
metric: {
id: '1',
key: MetricKey.lines_to_cover,
- type: 'INT',
+ type: MetricType.Integer,
name: 'Lines to Cover',
domain: 'Coverage',
},
metric: {
id: '2',
key: MetricKey.coverage,
- type: 'PERCENT',
+ type: MetricType.Percent,
name: 'Coverage',
domain: 'Coverage',
},
metric: {
id: '3',
key: MetricKey.duplicated_lines_density,
- type: 'PERCENT',
+ type: MetricType.Percent,
name: 'Duplicated Lines (%)',
domain: 'Duplications',
},
it('should exclude banned measures', () => {
expect(
utils.filterMeasures([
- { metric: { id: '1', key: MetricKey.open_issues, name: 'Bugs', type: 'INT' } },
+ { metric: { key: MetricKey.open_issues, name: 'Bugs', type: MetricType.Integer } },
{
metric: {
- id: '2',
key: MetricKey.critical_violations,
name: 'Critical Violations',
- type: 'INT',
+ type: MetricType.Integer,
},
},
]),
utils.sortMeasures('Reliability', [
{
metric: {
- id: '1',
key: MetricKey.reliability_remediation_effort,
- name: 'new_bugs',
- type: 'INT',
+ name: 'New bugs',
+ type: MetricType.Integer,
},
},
{
metric: {
- id: '2',
key: MetricKey.new_reliability_remediation_effort,
- name: 'bugs',
- type: 'INT',
+ name: 'Bugs',
+ type: MetricType.Integer,
},
},
- { metric: { id: '3', key: MetricKey.new_bugs, name: 'new_bugs', type: 'INT' } },
- { metric: { id: '4', key: MetricKey.bugs, name: 'bugs', type: 'INT' } },
+ { metric: { key: MetricKey.new_bugs, name: 'New bugs', type: MetricType.Integer } },
+ { metric: { key: MetricKey.bugs, name: 'Bugs', type: MetricType.Integer } },
'overall_category',
]),
).toMatchSnapshot();
name: 'TEST',
measures: [
{
- metric: 'alert_status',
+ metric: MetricKey.alert_status,
value: '3.2',
period: { index: 1, value: '0.0' },
},
{
- metric: 'releasability_rating',
+ metric: MetricKey.releasability_rating,
value: '3.2',
period: { index: 1, value: '0.0' },
},
{
- metric: 'releasability_effort',
+ metric: MetricKey.releasability_effort,
value: '3.2',
period: { index: 1, value: '0.0' },
},
const measure = utils.banQualityGateMeasure(componentBuilder(ComponentQualifier.File));
expect(measure).toHaveLength(2);
measure.forEach(({ metric }) => {
- expect(['releasability_rating', 'releasability_effort']).toContain(metric);
+ expect([MetricKey.releasability_rating, MetricKey.releasability_effort]).toContain(metric);
});
});
});
{isPullRequest(branchLike) ? (
<main>
<Suggestions suggestions="pull_requests" />
- <PullRequestOverview branchLike={branchLike} component={component} />
+ <PullRequestOverview pullRequest={branchLike} component={component} />
</main>
) : (
<main>
--- /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 {
+ Card,
+ HelperHintIcon,
+ LightLabel,
+ PopupPlacement,
+ SnoozeCircleIcon,
+ TextError,
+ TextSubdued,
+ themeColor,
+ Tooltip,
+ TrendDownCircleIcon,
+ TrendUpCircleIcon,
+} from 'design-system';
+import * as React from 'react';
+import { useIntl } from 'react-intl';
+import { getLeakValue } from '../../../components/measure/utils';
+import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
+import { getBranchLikeQuery } from '../../../helpers/branch-like';
+import { findMeasure, formatMeasure } from '../../../helpers/measures';
+import { getComponentIssuesUrl } from '../../../helpers/urls';
+import { PullRequest } from '../../../types/branch-like';
+import { MetricKey, MetricType } from '../../../types/metrics';
+import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
+import { Component, MeasureEnhanced } from '../../../types/types';
+import { getConditionRequiredLabel, Status } from '../utils';
+import { IssueMeasuresCardInner } from './IssueMeasuresCardInner';
+
+interface Props {
+ conditions: QualityGateStatusConditionEnhanced[];
+ measures: MeasureEnhanced[];
+ component: Component;
+ pullRequest: PullRequest;
+}
+
+export default function IssueMeasuresCard(
+ props: React.PropsWithChildren<Props & React.HTMLAttributes<HTMLDivElement>>,
+) {
+ const { measures, conditions, component, pullRequest, ...rest } = props;
+
+ const intl = useIntl();
+
+ const issuesCount = getLeakValue(findMeasure(measures, MetricKey.new_violations));
+ const issuesCondition = conditions.find((c) => c.metric === MetricKey.new_violations);
+ const issuesConditionFailed = issuesCondition?.level === Status.ERROR;
+ const fixedCount = findMeasure(measures, MetricKey.pull_request_fixed_issues)?.value;
+ const acceptedCount = getLeakValue(findMeasure(measures, MetricKey.new_accepted_issues));
+
+ const issuesUrl = getComponentIssuesUrl(component.key, {
+ ...getBranchLikeQuery(pullRequest),
+ ...DEFAULT_ISSUES_QUERY,
+ });
+ const fixedUrl = getComponentIssuesUrl(component.key, {
+ branch: pullRequest.target,
+ fixedInPR: pullRequest.key,
+ });
+ const acceptedUrl = getComponentIssuesUrl(component.key, {
+ ...getBranchLikeQuery(pullRequest),
+ ...DEFAULT_ISSUES_QUERY,
+ issueStatuses: 'ACCEPTED',
+ });
+
+ return (
+ <Card className="sw-p-8 sw-rounded-2 sw-flex sw-text-base sw-gap-4" {...rest}>
+ <IssueMeasuresCardInner
+ header={intl.formatMessage({ id: 'overview.pull_request.new_issues' })}
+ data-test="overview__measures-new-violations"
+ data-guiding-id={issuesConditionFailed ? 'overviewZeroNewIssuesSimplification' : undefined}
+ metric={MetricKey.new_violations}
+ value={formatMeasure(issuesCount, MetricType.ShortInteger)}
+ url={issuesUrl}
+ failed={issuesConditionFailed}
+ icon={issuesConditionFailed && <TrendUpCircleIcon />}
+ footer={
+ issuesCondition &&
+ (issuesConditionFailed ? (
+ <TextError
+ className="sw-font-regular sw-body-xs sw-inline"
+ text={getConditionRequiredLabel(issuesCondition, intl, true)}
+ />
+ ) : (
+ <LightLabel className="sw-body-xs">
+ {getConditionRequiredLabel(issuesCondition, intl)}
+ </LightLabel>
+ ))
+ }
+ />
+ <StyledCardSeparator />
+ <IssueMeasuresCardInner
+ header={intl.formatMessage({ id: 'overview.pull_request.accepted_issues' })}
+ metric={MetricKey.new_accepted_issues}
+ value={formatMeasure(acceptedCount, MetricType.ShortInteger)}
+ url={acceptedUrl}
+ icon={acceptedCount !== '0' && <SnoozeCircleIcon />}
+ footer={
+ <TextSubdued className="sw-body-xs">
+ {intl.formatMessage({ id: 'overview.pull_request.accepted_issues.help' })}
+ </TextSubdued>
+ }
+ />
+ <StyledCardSeparator />
+ <IssueMeasuresCardInner
+ header={
+ <>
+ {intl.formatMessage({ id: 'overview.pull_request.fixed_issues' })}
+ <Tooltip
+ overlay={
+ <div className="sw-flex sw-flex-col sw-gap-4">
+ <span>
+ {intl.formatMessage({ id: 'overview.pull_request.fixed_issues.disclaimer' })}
+ </span>
+ <span>
+ {intl.formatMessage({ id: 'overview.pull_request.fixed_issues.disclaimer.2' })}
+ </span>
+ </div>
+ }
+ placement={PopupPlacement.Top}
+ >
+ <HelperHintIcon raised />
+ </Tooltip>
+ </>
+ }
+ metric={MetricKey.pull_request_fixed_issues}
+ value={formatMeasure(fixedCount, MetricType.ShortInteger)}
+ url={fixedUrl}
+ icon={fixedCount !== '0' && <TrendDownCircleIcon />}
+ footer={
+ <TextSubdued className="sw-body-xs">
+ {intl.formatMessage({ id: 'overview.pull_request.fixed_issues.help' })}
+ </TextSubdued>
+ }
+ />
+ </Card>
+ );
+}
+
+const StyledCardSeparator = styled.div`
+ width: 1px;
+ background-color: ${themeColor('projectCardBorder')};
+`;
--- /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 { Badge, ContentLink } from 'design-system';
+import * as React from 'react';
+import { Path } from 'react-router-dom';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { localizeMetric } from '../../../helpers/measures';
+import { MetricKey } from '../../../types/metrics';
+
+interface IssueMeasuresCardInnerProps extends React.HTMLAttributes<HTMLDivElement> {
+ metric: MetricKey;
+ value?: string;
+ header: React.ReactNode;
+ url: Path;
+ failed?: boolean;
+ icon?: React.ReactNode;
+ footer?: React.ReactNode;
+}
+
+export function IssueMeasuresCardInner(props: Readonly<IssueMeasuresCardInnerProps>) {
+ const { header, metric, icon, value, url, failed, footer, ...rest } = props;
+
+ return (
+ <div className="sw-w-1/3 sw-flex sw-flex-col sw-gap-3" {...rest}>
+ <div className="sw-flex sw-flex-col sw-gap-2 sw-font-semibold">
+ <div className="sw-flex sw-items-center sw-gap-2">
+ {header}
+
+ {failed && (
+ <Badge className="sw-h-fit" variant="deleted">
+ {translate('overview.measures.failed_badge')}
+ </Badge>
+ )}
+ </div>
+ <div className="sw-flex sw-justify-between sw-items-center sw-h-9">
+ <div className="sw-h-fit">
+ <ContentLink
+ aria-label={translateWithParameters(
+ 'overview.see_more_details_on_x_of_y',
+ value ?? '0',
+ localizeMetric(metric),
+ )}
+ className="it__overview-measures-value sw-w-fit sw-text-lg"
+ to={url}
+ >
+ {value ?? '0'}
+ </ContentLink>
+ </div>
+
+ {icon}
+ </div>
+ </div>
+ {footer}
+ </div>
+ );
+}
import { formatMeasure } from '../../../helpers/measures';
import { MetricKey, MetricType } from '../../../types/metrics';
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
-import { Status } from '../utils';
+import { Status, getConditionRequiredLabel } from '../utils';
import MeasuresCard from './MeasuresCard';
interface Props {
url: To;
value: string;
conditionMetric: MetricKey;
- guidingKeyOnError?: string;
}
export default function MeasuresCardNumber(
props: React.PropsWithChildren<Props & React.HTMLAttributes<HTMLDivElement>>,
) {
- const { label, value, conditions, url, conditionMetric, guidingKeyOnError, ...rest } = props;
+ const { label, value, conditions, url, conditionMetric, ...rest } = props;
const intl = useIntl();
const conditionFailed = condition?.level === Status.ERROR;
- const requireLabel =
- condition &&
- intl.formatMessage(
- { id: 'overview.quality_gate.required_x' },
- {
- operator: condition.op === 'GT' ? '≤' : '≥',
- value: formatMeasure(condition.error, MetricType.Integer),
- },
- );
-
return (
<MeasuresCard
url={url}
metric={conditionMetric}
label={label}
failed={conditionFailed}
- data-guiding-id={conditionFailed ? guidingKeyOnError : undefined}
{...rest}
>
<span className="sw-body-xs sw-mt-3">
- {requireLabel &&
+ {condition &&
(conditionFailed ? (
- <TextError className="sw-font-regular" text={requireLabel} />
+ <TextError
+ className="sw-font-regular sw-inline"
+ text={getConditionRequiredLabel(condition, intl, true)}
+ />
) : (
- <LightLabel>{requireLabel}</LightLabel>
+ <LightLabel>{getConditionRequiredLabel(condition, intl)}</LightLabel>
))}
</span>
</MeasuresCard>
import classNames from 'classnames';
import * as React from 'react';
import { getLeakValue } from '../../../components/measure/utils';
-import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/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 { getComponentDrilldownUrl, getComponentSecurityHotspotsUrl } from '../../../helpers/urls';
+import { PullRequest } 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 IssueMeasuresCard from './IssueMeasuresCard';
import MeasuresCardNumber from './MeasuresCardNumber';
import MeasuresCardPercent from './MeasuresCardPercent';
interface Props {
className?: string;
- branchLike?: BranchLike;
+ pullRequest: PullRequest;
component: Component;
measures: MeasureEnhanced[];
conditions: QualityGateStatusConditionEnhanced[];
}
export default function MeasuresCardPanel(props: React.PropsWithChildren<Props>) {
- const { branchLike, component, measures, conditions, className } = props;
+ const { pullRequest, component, measures, conditions, className } = props;
- 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-w-full sw-flex sw-flex-row sw-gap-4 sw-mt-4', className)}>
- <div className="sw-flex-1 sw-flex sw-flex-col sw-gap-4">
- <MeasuresCardNumber
- data-test="overview__measures-new-violations"
- label={newViolations === '1' ? 'issue' : 'issues'}
- url={getComponentIssuesUrl(component.key, {
- ...getBranchLikeQuery(branchLike),
- ...DEFAULT_ISSUES_QUERY,
- })}
- value={newViolations}
- conditions={conditions}
- conditionMetric={MetricKey.new_violations}
- guidingKeyOnError="overviewZeroNewIssuesSimplification"
- />
+ <>
+ <IssueMeasuresCard
+ conditions={conditions}
+ measures={measures}
+ component={component}
+ pullRequest={pullRequest}
+ />
- <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,
- })}
- conditions={conditions}
- conditionMetric={MetricKey.new_coverage}
- newLinesMetric={MetricKey.new_lines_to_cover}
- afterMergeMetric={MetricKey.coverage}
- measures={measures}
- />
- </div>
+ <div className={classNames('sw-w-full sw-flex sw-flex-row sw-gap-4 sw-mt-4', className)}>
+ <div className="sw-flex-1 sw-flex sw-flex-col sw-gap-4">
+ <MeasuresCardPercent
+ componentKey={component.key}
+ branchLike={pullRequest}
+ measurementType={MeasurementType.Coverage}
+ label="overview.quality_gate.coverage"
+ url={getComponentDrilldownUrl({
+ componentKey: component.key,
+ metric: getMeasurementMetricKey(MeasurementType.Coverage, true),
+ branchLike: pullRequest,
+ listView: true,
+ })}
+ conditions={conditions}
+ conditionMetric={MetricKey.new_coverage}
+ newLinesMetric={MetricKey.new_lines_to_cover}
+ measures={measures}
+ />
- <div className="sw-flex-1 sw-flex sw-flex-col sw-gap-4">
- <MeasuresCardNumber
- label={
- newSecurityHotspots === '1'
- ? 'issue.type.SECURITY_HOTSPOT'
- : 'issue.type.SECURITY_HOTSPOT.plural'
- }
- url={getComponentSecurityHotspotsUrl(component.key, {
- ...getBranchLikeQuery(branchLike),
- })}
- value={newSecurityHotspots}
- conditions={conditions}
- conditionMetric={MetricKey.new_security_hotspots_reviewed}
- />
+ <MeasuresCardNumber
+ label={
+ newSecurityHotspots === '1'
+ ? 'issue.type.SECURITY_HOTSPOT'
+ : 'issue.type.SECURITY_HOTSPOT.plural'
+ }
+ url={getComponentSecurityHotspotsUrl(component.key, {
+ ...getBranchLikeQuery(pullRequest),
+ })}
+ value={newSecurityHotspots}
+ conditions={conditions}
+ conditionMetric={MetricKey.new_security_hotspots_reviewed}
+ />
+ </div>
- <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,
- })}
- conditions={conditions}
- conditionMetric={MetricKey.new_duplicated_lines_density}
- newLinesMetric={MetricKey.new_lines}
- afterMergeMetric={MetricKey.duplicated_lines_density}
- measures={measures}
- />
+ <div className="sw-flex-1 sw-flex sw-flex-col sw-gap-4">
+ <MeasuresCardPercent
+ componentKey={component.key}
+ branchLike={pullRequest}
+ measurementType={MeasurementType.Duplication}
+ label="overview.quality_gate.duplications"
+ url={getComponentDrilldownUrl({
+ componentKey: component.key,
+ metric: getMeasurementMetricKey(MeasurementType.Duplication, true),
+ branchLike: pullRequest,
+ listView: true,
+ })}
+ conditions={conditions}
+ conditionMetric={MetricKey.new_duplicated_lines_density}
+ newLinesMetric={MetricKey.new_lines}
+ measures={measures}
+ />
+ </div>
</div>
- </div>
+ </>
);
}
import { MetricKey, MetricType } from '../../../types/metrics';
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
import { MeasureEnhanced } from '../../../types/types';
-import { MeasurementType, Status, getMeasurementMetricKey } from '../utils';
+import {
+ MeasurementType,
+ Status,
+ getConditionRequiredLabel,
+ getMeasurementMetricKey,
+} from '../utils';
import MeasuresCard from './MeasuresCard';
interface Props {
conditions: QualityGateStatusConditionEnhanced[];
conditionMetric: MetricKey;
newLinesMetric: MetricKey;
- afterMergeMetric: MetricKey;
}
export default function MeasuresCardPercent(
conditions,
conditionMetric,
newLinesMetric,
- afterMergeMetric,
} = props;
const intl = useIntl();
listView: true,
});
- const afterMergeValue = findMeasure(measures, afterMergeMetric)?.value;
-
const condition = conditions.find((c) => c.metric === conditionMetric);
const conditionFailed = condition?.level === Status.ERROR;
- const requireLabel =
- condition &&
- intl.formatMessage(
- { id: 'overview.quality_gate.required_x' },
- {
- operator: condition.op === 'GT' ? '≤' : '≥',
- value: formatMeasure(condition.error, MetricType.Percent, {
- decimals: 2,
- omitExtraDecimalZeros: true,
- }),
- },
- );
-
return (
<MeasuresCard
value={formatMeasure(value, MetricType.Percent)}
>
<>
<span className="sw-body-xs sw-mt-3">
- {requireLabel &&
+ {condition &&
(conditionFailed ? (
- <TextError className="sw-font-regular" text={requireLabel} />
+ <TextError
+ className="sw-font-regular sw-inline"
+ text={getConditionRequiredLabel(condition, intl, true)}
+ />
) : (
- <LightLabel>{requireLabel}</LightLabel>
+ <LightLabel>{getConditionRequiredLabel(condition, intl)}</LightLabel>
))}
</span>
- <div className="sw-flex sw-justify-between sw-items-center sw-mt-1">
+ <div className="sw-flex sw-body-sm sw-justify-between sw-items-center sw-mt-1">
<LightLabel className="sw-flex sw-items-center sw-gap-1 ">
<FormattedMessage
defaultMessage={translate(newLinesLabel)}
}}
/>
</LightLabel>
- <LightLabel className="sw-mt-[1px]">
- {afterMergeValue && (
- <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>
</div>
</>
</MeasuresCard>
import SonarLintAd from './SonarLintAd';
interface Props {
- branchLike: PullRequest;
+ pullRequest: PullRequest;
component: Component;
}
-export default function PullRequestOverview(props: Readonly<Props>) {
- const { component, branchLike } = props;
+export default function PullRequestOverview(props: Readonly<Readonly<Props>>) {
+ const { component, pullRequest } = props;
const {
data: { conditions, ignoredConditions, status } = {},
useComponentMeasuresWithMetricsQuery(
component.key,
uniq([...PR_METRICS, ...(conditions?.map((c) => c.metric) ?? [])]),
- getBranchLikeQuery(branchLike),
+ getBranchLikeQuery(pullRequest),
!isLoadingBranchStatusesData,
);
<CenteredLayout>
<PageContentFontWrapper className="it__pr-overview sw-mt-12 sw-mb-8 sw-grid sw-grid-cols-12 sw-body-sm">
<div className="sw-col-start-2 sw-col-span-10">
- <MetaTopBar branchLike={branchLike} measures={measures} />
+ <MetaTopBar branchLike={pullRequest} measures={measures} />
<BasicSeparator className="sw-my-4" />
{ignoredConditions && <IgnoredConditionWarning />}
{status && (
<BranchQualityGate
- branchLike={branchLike}
+ branchLike={pullRequest}
component={component}
status={status}
failedConditions={failedConditions}
<MeasuresCardPanel
className="sw-flex-1"
- branchLike={branchLike}
+ pullRequest={pullRequest}
component={component}
conditions={enhancedConditions}
measures={measures}
mockMeasure({
metric: MetricKey.new_violations,
}),
+ mockMeasure({
+ metric: MetricKey.pull_request_fixed_issues,
+ }),
],
},
metrics: [
mockMetric({ key: MetricKey.new_lines, type: MetricType.ShortInteger }),
mockMetric({ key: MetricKey.new_bugs, type: MetricType.Integer }),
mockMetric({ key: MetricKey.new_violations }),
+ mockMetric({ key: MetricKey.pull_request_fixed_issues }),
],
}),
};
};
});
+it('should render links correctly', async () => {
+ jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({
+ status: 'OK',
+ conditions: [],
+ caycStatus: CaycStatus.Compliant,
+ ignoredConditions: false,
+ });
+ renderPullRequestOverview();
+
+ await waitFor(async () => expect(await screen.findByText('metric.level.OK')).toBeInTheDocument());
+ expect(screen.getByLabelText('overview.quality_gate_x.overview.gate.OK')).toBeInTheDocument();
+
+ expect(
+ byRole('link', {
+ name: 'overview.see_more_details_on_x_of_y.1.metric.new_violations.name',
+ }).get(),
+ ).toHaveAttribute(
+ 'href',
+ '/project/issues?pullRequest=1001&issueStatuses=OPEN%2CCONFIRMED&id=foo',
+ );
+
+ expect(
+ byRole('link', {
+ name: 'overview.see_more_details_on_x_of_y.1.metric.pull_request_fixed_issues.name',
+ }).get(),
+ ).toHaveAttribute('href', '/project/issues?branch=master&fixedInPullRequest=1001&id=foo');
+
+ expect(screen.getByLabelText('no_data')).toBeInTheDocument();
+});
+
it('should render correctly for a passed QG', async () => {
jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({
status: 'OK',
renderComponent(
<CurrentUserContextProvider currentUser={currentUser}>
<PullRequestOverview
- branchLike={mockPullRequest()}
+ pullRequest={mockPullRequest()}
component={mockComponent({
breadcrumbs: [mockComponent({ key: 'foo' })],
key: 'foo',
+++ /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 { memoize } from 'lodash';
-import CoverageRating from '../../components/ui/CoverageRating';
-import DuplicationsRating from '../../components/ui/DuplicationsRating';
-import { ISSUETYPE_METRIC_KEYS_MAP } from '../../helpers/issues';
-import { translate } from '../../helpers/l10n';
-import { parseAsString } from '../../helpers/query';
-import { IssueType } from '../../types/issues';
-import { MetricKey } from '../../types/metrics';
-import { AnalysisMeasuresVariations, MeasureHistory } from '../../types/project-activity';
-import { Dict, RawQuery } from '../../types/types';
-
-export const METRICS: string[] = [
- // quality gate
- MetricKey.alert_status,
- MetricKey.quality_gate_details, // TODO: still relevant?
-
- // bugs
- MetricKey.bugs,
- MetricKey.new_bugs,
- MetricKey.reliability_rating,
- MetricKey.new_reliability_rating,
-
- // vulnerabilities
- MetricKey.vulnerabilities,
- MetricKey.new_vulnerabilities,
- MetricKey.security_rating,
- MetricKey.new_security_rating,
-
- // hotspots
- MetricKey.security_hotspots,
- MetricKey.new_security_hotspots,
- MetricKey.security_hotspots_reviewed,
- MetricKey.new_security_hotspots_reviewed,
- MetricKey.security_review_rating,
- MetricKey.new_security_review_rating,
-
- // code smells
- MetricKey.code_smells,
- MetricKey.new_code_smells,
- MetricKey.sqale_rating,
- MetricKey.new_maintainability_rating,
- MetricKey.sqale_index,
- MetricKey.new_technical_debt,
-
- // coverage
- MetricKey.coverage,
- MetricKey.new_coverage,
- MetricKey.lines_to_cover,
- MetricKey.new_lines_to_cover,
- MetricKey.tests,
-
- // duplications
- MetricKey.duplicated_lines_density,
- MetricKey.new_duplicated_lines_density,
- MetricKey.duplicated_blocks,
-
- // size
- MetricKey.ncloc,
- MetricKey.ncloc_language_distribution,
- MetricKey.projects,
- MetricKey.lines,
- MetricKey.new_lines,
-];
-
-export const PR_METRICS: string[] = [
- MetricKey.coverage,
- MetricKey.new_coverage,
- MetricKey.new_lines_to_cover,
-
- MetricKey.new_violations,
- MetricKey.duplicated_lines_density,
- MetricKey.new_duplicated_lines_density,
- MetricKey.new_lines,
- MetricKey.new_code_smells,
- MetricKey.new_maintainability_rating,
- MetricKey.new_bugs,
- MetricKey.new_reliability_rating,
- MetricKey.new_vulnerabilities,
- MetricKey.new_security_hotspots,
- MetricKey.new_security_review_rating,
- MetricKey.new_security_rating,
-];
-
-export const HISTORY_METRICS_LIST: string[] = [
- MetricKey.bugs,
- MetricKey.vulnerabilities,
- MetricKey.sqale_index,
- MetricKey.duplicated_lines_density,
- MetricKey.ncloc,
- MetricKey.coverage,
- MetricKey.alert_status,
-];
-
-const MEASURES_VARIATIONS_METRICS = [
- MetricKey.bugs,
- MetricKey.code_smells,
- MetricKey.coverage,
- MetricKey.duplicated_lines_density,
- MetricKey.vulnerabilities,
-];
-
-export enum MeasurementType {
- Coverage = 'COVERAGE',
- Duplication = 'DUPLICATION',
-}
-
-export enum Status {
- OK = 'OK',
- ERROR = 'ERROR',
-}
-
-const MEASUREMENTS_MAP = {
- [MeasurementType.Coverage]: {
- metric: MetricKey.coverage,
- newMetric: MetricKey.new_coverage,
- linesMetric: MetricKey.lines_to_cover,
- newLinesMetric: MetricKey.new_lines_to_cover,
- afterMergeMetric: MetricKey.coverage,
- labelKey: 'metric.coverage.name',
- expandedLabelKey: 'overview.coverage_on_X_lines',
- newLinesExpandedLabelKey: 'overview.coverage_on_X_new_lines',
- iconClass: CoverageRating,
- },
- [MeasurementType.Duplication]: {
- metric: MetricKey.duplicated_lines_density,
- newMetric: MetricKey.new_duplicated_lines_density,
- linesMetric: MetricKey.ncloc,
- newLinesMetric: MetricKey.new_lines,
- afterMergeMetric: MetricKey.duplicated_lines_density,
- labelKey: 'metric.duplicated_lines_density.short_name',
- expandedLabelKey: 'overview.duplications_on_X_lines',
- newLinesExpandedLabelKey: 'overview.duplications_on_X_new_lines',
- iconClass: DuplicationsRating,
- },
-};
-
-export const RATING_TO_SEVERITIES_MAPPING = [
- 'BLOCKER,CRITICAL,MAJOR,MINOR',
- 'BLOCKER,CRITICAL,MAJOR',
- 'BLOCKER,CRITICAL',
- 'BLOCKER',
-];
-
-export const RATING_METRICS_MAPPING: Dict<IssueType> = {
- [MetricKey.reliability_rating]: IssueType.Bug,
- [MetricKey.new_reliability_rating]: IssueType.Bug,
- [MetricKey.security_rating]: IssueType.Vulnerability,
- [MetricKey.new_security_rating]: IssueType.Vulnerability,
- [MetricKey.sqale_rating]: IssueType.CodeSmell,
- [MetricKey.new_maintainability_rating]: IssueType.CodeSmell,
- [MetricKey.security_review_rating]: IssueType.SecurityHotspot,
- [MetricKey.new_security_review_rating]: IssueType.SecurityHotspot,
-};
-
-export const METRICS_REPORTED_IN_OVERVIEW_CARDS = [
- MetricKey.new_violations,
- MetricKey.violations,
- MetricKey.new_coverage,
- MetricKey.coverage,
- MetricKey.new_security_hotspots_reviewed,
- MetricKey.security_hotspots_reviewed,
- MetricKey.new_duplicated_lines_density,
- MetricKey.duplicated_lines_density,
-];
-
-export function getIssueRatingName(type: IssueType) {
- return translate('metric_domain', ISSUETYPE_METRIC_KEYS_MAP[type].ratingName);
-}
-
-export function getIssueIconClass(type: IssueType) {
- return ISSUETYPE_METRIC_KEYS_MAP[type].iconClass;
-}
-
-export function getIssueMetricKey(type: IssueType, useDiffMetric: boolean) {
- return useDiffMetric
- ? ISSUETYPE_METRIC_KEYS_MAP[type].newMetric
- : ISSUETYPE_METRIC_KEYS_MAP[type].metric;
-}
-
-export function getIssueRatingMetricKey(type: IssueType, useDiffMetric: boolean) {
- return useDiffMetric
- ? ISSUETYPE_METRIC_KEYS_MAP[type].newRating
- : ISSUETYPE_METRIC_KEYS_MAP[type].rating;
-}
-
-export function getMeasurementIconClass(type: MeasurementType) {
- return MEASUREMENTS_MAP[type].iconClass;
-}
-
-export function getMeasurementMetricKey(type: MeasurementType, useDiffMetric: boolean) {
- return useDiffMetric ? MEASUREMENTS_MAP[type].newMetric : MEASUREMENTS_MAP[type].metric;
-}
-
-export function getMeasurementAfterMergeMetricKey(type: MeasurementType) {
- return MEASUREMENTS_MAP[type].afterMergeMetric;
-}
-
-export function getMeasurementLinesMetricKey(type: MeasurementType, useDiffMetric: boolean) {
- return useDiffMetric ? MEASUREMENTS_MAP[type].newLinesMetric : MEASUREMENTS_MAP[type].linesMetric;
-}
-
-export function getMeasurementLabelKeys(type: MeasurementType, useDiffMetric: boolean) {
- return {
- expandedLabelKey: useDiffMetric
- ? MEASUREMENTS_MAP[type].newLinesExpandedLabelKey
- : MEASUREMENTS_MAP[type].expandedLabelKey,
- labelKey: MEASUREMENTS_MAP[type].labelKey,
- };
-}
-
-export const parseQuery = memoize((urlQuery: RawQuery): { codeScope: string } => {
- return {
- codeScope: parseAsString(urlQuery['code_scope']),
- };
-});
-
-export function getAnalysisVariations(measures: MeasureHistory[], analysesCount: number) {
- if (analysesCount === 0) {
- return [];
- }
-
- const emptyVariations: AnalysisMeasuresVariations[] = Array.from(
- { length: analysesCount },
- () => ({}),
- );
-
- return measures.reduce((variations, { metric, history }) => {
- if (!MEASURES_VARIATIONS_METRICS.includes(metric)) {
- return variations;
- }
-
- history.slice(-analysesCount).forEach(({ value = '' }, index, analysesHistory) => {
- if (index === 0) {
- variations[index][metric] = parseFloat(value) || 0;
- return;
- }
-
- const previousValue = parseFloat(analysesHistory[index - 1].value ?? '') || 0;
- const numericValue = parseFloat(value) || 0;
- const variation = numericValue - previousValue;
-
- if (variation === 0) {
- return;
- }
-
- variations[index][metric] = variation;
- });
-
- return variations;
- }, emptyVariations);
-}
--- /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 { memoize } from 'lodash';
+import React from 'react';
+import { IntlShape } from 'react-intl';
+import CoverageRating from '../../components/ui/CoverageRating';
+import DuplicationsRating from '../../components/ui/DuplicationsRating';
+import { ISSUETYPE_METRIC_KEYS_MAP } from '../../helpers/issues';
+import { translate } from '../../helpers/l10n';
+import { formatMeasure } from '../../helpers/measures';
+import { parseAsString } from '../../helpers/query';
+import { IssueType } from '../../types/issues';
+import { MetricKey } from '../../types/metrics';
+import { AnalysisMeasuresVariations, MeasureHistory } from '../../types/project-activity';
+import { QualityGateStatusConditionEnhanced } from '../../types/quality-gates';
+import { Dict, RawQuery } from '../../types/types';
+
+export const METRICS: string[] = [
+ // quality gate
+ MetricKey.alert_status,
+ MetricKey.quality_gate_details, // TODO: still relevant?
+
+ // bugs
+ MetricKey.bugs,
+ MetricKey.new_bugs,
+ MetricKey.reliability_rating,
+ MetricKey.new_reliability_rating,
+
+ // vulnerabilities
+ MetricKey.vulnerabilities,
+ MetricKey.new_vulnerabilities,
+ MetricKey.security_rating,
+ MetricKey.new_security_rating,
+
+ // hotspots
+ MetricKey.security_hotspots,
+ MetricKey.new_security_hotspots,
+ MetricKey.security_hotspots_reviewed,
+ MetricKey.new_security_hotspots_reviewed,
+ MetricKey.security_review_rating,
+ MetricKey.new_security_review_rating,
+
+ // code smells
+ MetricKey.code_smells,
+ MetricKey.new_code_smells,
+ MetricKey.sqale_rating,
+ MetricKey.new_maintainability_rating,
+ MetricKey.sqale_index,
+ MetricKey.new_technical_debt,
+
+ // coverage
+ MetricKey.coverage,
+ MetricKey.new_coverage,
+ MetricKey.lines_to_cover,
+ MetricKey.new_lines_to_cover,
+ MetricKey.tests,
+
+ // duplications
+ MetricKey.duplicated_lines_density,
+ MetricKey.new_duplicated_lines_density,
+ MetricKey.duplicated_blocks,
+
+ // size
+ MetricKey.ncloc,
+ MetricKey.ncloc_language_distribution,
+ MetricKey.projects,
+ MetricKey.lines,
+ MetricKey.new_lines,
+];
+
+export const PR_METRICS: string[] = [
+ MetricKey.coverage,
+ MetricKey.new_coverage,
+ MetricKey.new_lines_to_cover,
+
+ MetricKey.new_accepted_issues,
+ MetricKey.new_violations,
+ MetricKey.duplicated_lines_density,
+ MetricKey.new_duplicated_lines_density,
+ MetricKey.new_lines,
+ MetricKey.new_code_smells,
+ MetricKey.new_maintainability_rating,
+ MetricKey.new_bugs,
+ MetricKey.new_reliability_rating,
+ MetricKey.new_vulnerabilities,
+ MetricKey.new_security_hotspots,
+ MetricKey.new_security_review_rating,
+ MetricKey.new_security_rating,
+
+ MetricKey.pull_request_fixed_issues,
+];
+
+export const HISTORY_METRICS_LIST: string[] = [
+ MetricKey.bugs,
+ MetricKey.vulnerabilities,
+ MetricKey.sqale_index,
+ MetricKey.duplicated_lines_density,
+ MetricKey.ncloc,
+ MetricKey.coverage,
+ MetricKey.alert_status,
+];
+
+const MEASURES_VARIATIONS_METRICS = [
+ MetricKey.bugs,
+ MetricKey.code_smells,
+ MetricKey.coverage,
+ MetricKey.duplicated_lines_density,
+ MetricKey.vulnerabilities,
+];
+
+export enum MeasurementType {
+ Coverage = 'COVERAGE',
+ Duplication = 'DUPLICATION',
+}
+
+export enum Status {
+ OK = 'OK',
+ ERROR = 'ERROR',
+}
+
+const MEASUREMENTS_MAP = {
+ [MeasurementType.Coverage]: {
+ metric: MetricKey.coverage,
+ newMetric: MetricKey.new_coverage,
+ linesMetric: MetricKey.lines_to_cover,
+ newLinesMetric: MetricKey.new_lines_to_cover,
+ afterMergeMetric: MetricKey.coverage,
+ labelKey: 'metric.coverage.name',
+ expandedLabelKey: 'overview.coverage_on_X_lines',
+ newLinesExpandedLabelKey: 'overview.coverage_on_X_new_lines',
+ iconClass: CoverageRating,
+ },
+ [MeasurementType.Duplication]: {
+ metric: MetricKey.duplicated_lines_density,
+ newMetric: MetricKey.new_duplicated_lines_density,
+ linesMetric: MetricKey.ncloc,
+ newLinesMetric: MetricKey.new_lines,
+ afterMergeMetric: MetricKey.duplicated_lines_density,
+ labelKey: 'metric.duplicated_lines_density.short_name',
+ expandedLabelKey: 'overview.duplications_on_X_lines',
+ newLinesExpandedLabelKey: 'overview.duplications_on_X_new_lines',
+ iconClass: DuplicationsRating,
+ },
+};
+
+export const RATING_TO_SEVERITIES_MAPPING = [
+ 'BLOCKER,CRITICAL,MAJOR,MINOR',
+ 'BLOCKER,CRITICAL,MAJOR',
+ 'BLOCKER,CRITICAL',
+ 'BLOCKER',
+];
+
+export const RATING_METRICS_MAPPING: Dict<IssueType> = {
+ [MetricKey.reliability_rating]: IssueType.Bug,
+ [MetricKey.new_reliability_rating]: IssueType.Bug,
+ [MetricKey.security_rating]: IssueType.Vulnerability,
+ [MetricKey.new_security_rating]: IssueType.Vulnerability,
+ [MetricKey.sqale_rating]: IssueType.CodeSmell,
+ [MetricKey.new_maintainability_rating]: IssueType.CodeSmell,
+ [MetricKey.security_review_rating]: IssueType.SecurityHotspot,
+ [MetricKey.new_security_review_rating]: IssueType.SecurityHotspot,
+};
+
+export const METRICS_REPORTED_IN_OVERVIEW_CARDS = [
+ MetricKey.new_violations,
+ MetricKey.violations,
+ MetricKey.new_coverage,
+ MetricKey.coverage,
+ MetricKey.new_security_hotspots_reviewed,
+ MetricKey.security_hotspots_reviewed,
+ MetricKey.new_duplicated_lines_density,
+ MetricKey.duplicated_lines_density,
+];
+
+export function getIssueRatingName(type: IssueType) {
+ return translate('metric_domain', ISSUETYPE_METRIC_KEYS_MAP[type].ratingName);
+}
+
+export function getIssueIconClass(type: IssueType) {
+ return ISSUETYPE_METRIC_KEYS_MAP[type].iconClass;
+}
+
+export function getIssueMetricKey(type: IssueType, useDiffMetric: boolean) {
+ return useDiffMetric
+ ? ISSUETYPE_METRIC_KEYS_MAP[type].newMetric
+ : ISSUETYPE_METRIC_KEYS_MAP[type].metric;
+}
+
+export function getIssueRatingMetricKey(type: IssueType, useDiffMetric: boolean) {
+ return useDiffMetric
+ ? ISSUETYPE_METRIC_KEYS_MAP[type].newRating
+ : ISSUETYPE_METRIC_KEYS_MAP[type].rating;
+}
+
+export function getMeasurementIconClass(type: MeasurementType) {
+ return MEASUREMENTS_MAP[type].iconClass;
+}
+
+export function getMeasurementMetricKey(type: MeasurementType, useDiffMetric: boolean) {
+ return useDiffMetric ? MEASUREMENTS_MAP[type].newMetric : MEASUREMENTS_MAP[type].metric;
+}
+
+export function getMeasurementAfterMergeMetricKey(type: MeasurementType) {
+ return MEASUREMENTS_MAP[type].afterMergeMetric;
+}
+
+export function getMeasurementLinesMetricKey(type: MeasurementType, useDiffMetric: boolean) {
+ return useDiffMetric ? MEASUREMENTS_MAP[type].newLinesMetric : MEASUREMENTS_MAP[type].linesMetric;
+}
+
+export function getMeasurementLabelKeys(type: MeasurementType, useDiffMetric: boolean) {
+ return {
+ expandedLabelKey: useDiffMetric
+ ? MEASUREMENTS_MAP[type].newLinesExpandedLabelKey
+ : MEASUREMENTS_MAP[type].expandedLabelKey,
+ labelKey: MEASUREMENTS_MAP[type].labelKey,
+ };
+}
+
+export const parseQuery = memoize((urlQuery: RawQuery): { codeScope: string } => {
+ return {
+ codeScope: parseAsString(urlQuery['code_scope']),
+ };
+});
+
+export function getAnalysisVariations(measures: MeasureHistory[], analysesCount: number) {
+ if (analysesCount === 0) {
+ return [];
+ }
+
+ const emptyVariations: AnalysisMeasuresVariations[] = Array.from(
+ { length: analysesCount },
+ () => ({}),
+ );
+
+ return measures.reduce((variations, { metric, history }) => {
+ if (!MEASURES_VARIATIONS_METRICS.includes(metric)) {
+ return variations;
+ }
+
+ history.slice(-analysesCount).forEach(({ value = '' }, index, analysesHistory) => {
+ if (index === 0) {
+ variations[index][metric] = parseFloat(value) || 0;
+ return;
+ }
+
+ const previousValue = parseFloat(analysesHistory[index - 1].value ?? '') || 0;
+ const numericValue = parseFloat(value) || 0;
+ const variation = numericValue - previousValue;
+
+ if (variation === 0) {
+ return;
+ }
+
+ variations[index][metric] = variation;
+ });
+
+ return variations;
+ }, emptyVariations);
+}
+
+export function getConditionRequiredLabel(
+ condition: QualityGateStatusConditionEnhanced,
+ intl: IntlShape,
+ failed = false,
+) {
+ const conditionEl = (
+ <>
+ {condition.op === 'GT' ? '≤' : '≥'}{' '}
+ {formatMeasure(condition.error, condition.measure.metric.type, {
+ decimals: 2,
+ omitExtraDecimalZeros: true,
+ })}
+ </>
+ );
+ return intl.formatMessage(
+ { id: 'overview.quality_gate.required_x' },
+ {
+ requirement: failed ? <b>{conditionEl}</b> : conditionEl,
+ },
+ );
+}
export const DEFAULT_METRICS: Dict<Metric> = {
new_technical_debt: {
- id: 'AXJMbIl_PAOIsUIE3guE',
key: 'new_technical_debt',
type: 'WORK_DUR',
name: 'Added Technical Debt',
hidden: false,
},
blocker_violations: {
- id: 'AXJMbIl_PAOIsUIE3gtt',
key: 'blocker_violations',
type: 'INT',
name: 'Blocker Issues',
hidden: false,
},
bugs: {
- id: 'AXJMbIl_PAOIsUIE3gt_',
key: 'bugs',
type: 'INT',
name: 'Bugs',
hidden: false,
},
classes: {
- id: 'AXJMbImPPAOIsUIE3gu5',
key: 'classes',
type: 'INT',
name: 'Classes',
hidden: false,
},
code_smells: {
- id: 'AXJMbIl_PAOIsUIE3gt9',
key: 'code_smells',
type: 'INT',
name: 'Code Smells',
hidden: false,
},
cognitive_complexity: {
- id: 'AXJMbIl9PAOIsUIE3gtZ',
key: 'cognitive_complexity',
type: 'INT',
name: 'Cognitive Complexity',
hidden: false,
},
comment_lines: {
- id: 'AXJMbImPPAOIsUIE3gup',
key: 'comment_lines',
type: 'INT',
name: 'Comment Lines',
hidden: false,
},
comment_lines_data: {
- id: 'AXJMbImPPAOIsUIE3guV',
key: 'comment_lines_data',
type: 'DATA',
name: 'comment_lines_data',
hidden: true,
},
comment_lines_density: {
- id: 'AXJMbImPPAOIsUIE3guq',
key: 'comment_lines_density',
type: 'PERCENT',
name: 'Comments (%)',
decimalScale: 1,
},
class_complexity: {
- id: 'AXJMbImPPAOIsUIE3guw',
key: 'class_complexity',
type: 'FLOAT',
name: 'Complexity / Class',
decimalScale: 1,
},
file_complexity: {
- id: 'AXJMbImPPAOIsUIE3guu',
key: 'file_complexity',
type: 'FLOAT',
name: 'Complexity / File',
decimalScale: 1,
},
function_complexity: {
- id: 'AXJMbImPPAOIsUIE3guy',
key: 'function_complexity',
type: 'FLOAT',
name: 'Complexity / Function',
decimalScale: 1,
},
complexity_in_classes: {
- id: 'AXJMbImPPAOIsUIE3guv',
key: 'complexity_in_classes',
type: 'INT',
name: 'Complexity in Classes',
hidden: true,
},
complexity_in_functions: {
- id: 'AXJMbImPPAOIsUIE3gux',
key: 'complexity_in_functions',
type: 'INT',
name: 'Complexity in Functions',
hidden: true,
},
branch_coverage: {
- id: 'AXJMbIl9PAOIsUIE3gs-',
key: 'branch_coverage',
type: 'PERCENT',
name: 'Condition Coverage',
decimalScale: 1,
},
new_branch_coverage: {
- id: 'AXJMbIl9PAOIsUIE3gs_',
key: 'new_branch_coverage',
type: 'PERCENT',
name: 'Condition Coverage on New Code',
decimalScale: 1,
},
conditions_to_cover: {
- id: 'AXJMbIl9PAOIsUIE3gqt',
key: 'conditions_to_cover',
type: 'INT',
name: 'Conditions to Cover',
hidden: false,
},
new_conditions_to_cover: {
- id: 'AXJMbIl9PAOIsUIE3gs7',
key: 'new_conditions_to_cover',
type: 'INT',
name: 'Conditions to Cover on New Code',
hidden: false,
},
confirmed_issues: {
- id: 'AXJMbIl_PAOIsUIE3gt8',
key: 'confirmed_issues',
type: 'INT',
name: 'Confirmed Issues',
hidden: false,
},
coverage: {
- id: 'AXJMbIl9PAOIsUIE3gtg',
key: 'coverage',
type: 'PERCENT',
name: 'Coverage',
decimalScale: 1,
},
new_coverage: {
- id: 'AXJMbIl_PAOIsUIE3gth',
key: 'new_coverage',
type: 'PERCENT',
name: 'Coverage on New Code',
decimalScale: 1,
},
critical_violations: {
- id: 'AXJMbIl_PAOIsUIE3gtu',
key: 'critical_violations',
type: 'INT',
name: 'Critical Issues',
hidden: false,
},
complexity: {
- id: 'AXJMbImPPAOIsUIE3gut',
key: 'complexity',
type: 'INT',
name: 'Cyclomatic Complexity',
hidden: false,
},
last_commit_date: {
- id: 'AXJMbImPPAOIsUIE3gua',
key: 'last_commit_date',
type: 'MILLISEC',
name: 'Date of Last Commit',
hidden: true,
},
development_cost: {
- id: 'AXJMbIl_PAOIsUIE3guI',
key: 'development_cost',
type: 'STRING',
name: 'Development Cost',
hidden: true,
},
new_development_cost: {
- id: 'AXJMbIl_PAOIsUIE3guJ',
key: 'new_development_cost',
type: 'FLOAT',
name: 'Development Cost on New Code',
decimalScale: 1,
},
directories: {
- id: 'AXJMbImPPAOIsUIE3gu9',
key: 'directories',
type: 'INT',
name: 'Directories',
hidden: false,
},
duplicated_blocks: {
- id: 'AXJMbIl9PAOIsUIE3gsu',
key: 'duplicated_blocks',
type: 'INT',
name: 'Duplicated Blocks',
hidden: false,
},
new_duplicated_blocks: {
- id: 'AXJMbIl_PAOIsUIE3gto',
key: 'new_duplicated_blocks',
type: 'INT',
name: 'Duplicated Blocks on New Code',
hidden: false,
},
duplicated_files: {
- id: 'AXJMbImPPAOIsUIE3gvA',
key: 'duplicated_files',
type: 'INT',
name: 'Duplicated Files',
hidden: false,
},
duplicated_lines: {
- id: 'AXJMbIl9PAOIsUIE3gss',
key: 'duplicated_lines',
type: 'INT',
name: 'Duplicated Lines',
hidden: false,
},
duplicated_lines_density: {
- id: 'AXJMbIl_PAOIsUIE3gtp',
key: 'duplicated_lines_density',
type: 'PERCENT',
name: 'Duplicated Lines (%)',
decimalScale: 1,
},
new_duplicated_lines_density: {
- id: 'AXJMbIl_PAOIsUIE3gtq',
key: 'new_duplicated_lines_density',
type: 'PERCENT',
name: 'Duplicated Lines (%) on New Code',
decimalScale: 1,
},
new_duplicated_lines: {
- id: 'AXJMbIl9PAOIsUIE3gst',
key: 'new_duplicated_lines',
type: 'INT',
name: 'Duplicated Lines on New Code',
hidden: false,
},
duplications_data: {
- id: 'AXJMbIl_PAOIsUIE3gtr',
key: 'duplications_data',
type: 'DATA',
name: 'Duplication Details',
hidden: false,
},
effort_to_reach_maintainability_rating_a: {
- id: 'AXJMbIl_PAOIsUIE3guM',
key: 'effort_to_reach_maintainability_rating_a',
type: 'WORK_DUR',
name: 'Effort to Reach Maintainability Rating A',
hidden: false,
},
executable_lines_data: {
- id: 'AXJMbImPPAOIsUIE3guW',
key: 'executable_lines_data',
type: 'DATA',
name: 'executable_lines_data',
hidden: true,
},
false_positive_issues: {
- id: 'AXJMbIl_PAOIsUIE3gt4',
key: 'false_positive_issues',
type: 'INT',
name: 'False Positive Issues',
hidden: false,
},
file_complexity_distribution: {
- id: 'AXJMbIl9PAOIsUIE3gtY',
key: 'file_complexity_distribution',
type: 'DISTRIB',
name: 'File Distribution / Complexity',
hidden: true,
},
files: {
- id: 'AXJMbImPPAOIsUIE3gu6',
key: 'files',
type: 'INT',
name: 'Files',
hidden: false,
},
function_complexity_distribution: {
- id: 'AXJMbIl9PAOIsUIE3gtX',
key: 'function_complexity_distribution',
type: 'DISTRIB',
name: 'Function Distribution / Complexity',
hidden: true,
},
functions: {
- id: 'AXJMbImPPAOIsUIE3gu-',
key: 'functions',
type: 'INT',
name: 'Functions',
hidden: false,
},
generated_lines: {
- id: 'AXJMbImPPAOIsUIE3gu0',
key: 'generated_lines',
type: 'INT',
name: 'Generated Lines',
hidden: false,
},
generated_ncloc: {
- id: 'AXJMbImPPAOIsUIE3gu4',
key: 'generated_ncloc',
type: 'INT',
name: 'Generated Lines of Code',
hidden: false,
},
info_violations: {
- id: 'AXJMbIl_PAOIsUIE3gtx',
key: 'info_violations',
type: 'INT',
name: 'Info Issues',
hidden: false,
},
violations: {
- id: 'AXJMbImPPAOIsUIE3gul',
key: 'violations',
type: 'INT',
name: 'Issues',
hidden: false,
},
last_change_on_maintainability_rating: {
- id: 'AXJMbImPPAOIsUIE3gud',
key: 'last_change_on_maintainability_rating',
type: 'DATA',
name: 'Last Change on Maintainability Rating',
hidden: true,
},
last_change_on_releasability_rating: {
- id: 'AXJMbImPPAOIsUIE3gue',
key: 'last_change_on_releasability_rating',
type: 'DATA',
name: 'Last Change on Releasability Rating',
hidden: true,
},
last_change_on_reliability_rating: {
- id: 'AXJMbImPPAOIsUIE3guf',
key: 'last_change_on_reliability_rating',
type: 'DATA',
name: 'Last Change on Reliability Rating',
hidden: true,
},
last_change_on_security_rating: {
- id: 'AXJMbImPPAOIsUIE3gug',
key: 'last_change_on_security_rating',
type: 'DATA',
name: 'Last Change on Security Rating',
hidden: true,
},
last_change_on_security_review_rating: {
- id: 'AXJMbIl9PAOIsUIE3gs4',
key: 'last_change_on_security_review_rating',
type: 'DATA',
name: 'Last Change on Security Review Rating',
hidden: true,
},
line_coverage: {
- id: 'AXJMbIl_PAOIsUIE3gtl',
key: 'line_coverage',
type: 'PERCENT',
name: 'Line Coverage',
decimalScale: 1,
},
new_line_coverage: {
- id: 'AXJMbIl_PAOIsUIE3gtm',
key: 'new_line_coverage',
type: 'PERCENT',
name: 'Line Coverage on New Code',
decimalScale: 1,
},
lines: {
- id: 'AXJMbImPPAOIsUIE3guz',
key: 'lines',
type: 'INT',
name: 'Lines',
hidden: false,
},
ncloc: {
- id: 'AXJMbImPPAOIsUIE3gu1',
key: 'ncloc',
type: 'INT',
name: 'Lines of Code',
hidden: false,
},
ncloc_language_distribution: {
- id: 'AXJMbImPPAOIsUIE3gu3',
key: 'ncloc_language_distribution',
type: 'DATA',
name: 'Lines of Code Per Language',
hidden: false,
},
lines_to_cover: {
- id: 'AXJMbImPPAOIsUIE3gu_',
key: 'lines_to_cover',
type: 'INT',
name: 'Lines to Cover',
hidden: false,
},
new_lines_to_cover: {
- id: 'AXJMbIl_PAOIsUIE3gti',
key: 'new_lines_to_cover',
type: 'INT',
name: 'Lines to Cover on New Code',
hidden: false,
},
leak_projects: {
- id: 'AXJMbImPPAOIsUIE3gvE',
key: 'leak_projects',
type: 'DATA',
name: 'List of technical projects with their leaks',
hidden: true,
},
sqale_rating: {
- id: 'AXJMbIl_PAOIsUIE3guF',
key: 'sqale_rating',
type: 'RATING',
name: 'Maintainability Rating',
hidden: false,
},
maintainability_rating_distribution: {
- id: 'AX6QkqP7zEziun0YBqmh',
key: 'maintainability_rating_distribution',
type: 'DATA',
name: 'Maintainability Rating Distribution',
hidden: true,
},
new_maintainability_rating_distribution: {
- id: 'AX6QkqP8zEziun0YBqml',
key: 'new_maintainability_rating_distribution',
type: 'DATA',
name: 'Maintainability Rating Distribution on New Code',
hidden: true,
},
new_maintainability_rating: {
- id: 'AXJMbIl_PAOIsUIE3guH',
key: 'new_maintainability_rating',
type: 'RATING',
name: 'Maintainability Rating on New Code',
hidden: false,
},
major_violations: {
- id: 'AXJMbIl_PAOIsUIE3gtv',
key: 'major_violations',
type: 'INT',
name: 'Major Issues',
hidden: false,
},
minor_violations: {
- id: 'AXJMbIl_PAOIsUIE3gtw',
key: 'minor_violations',
type: 'INT',
name: 'Minor Issues',
hidden: false,
},
ncloc_data: {
- id: 'AXJMbImPPAOIsUIE3guU',
key: 'ncloc_data',
type: 'DATA',
name: 'ncloc_data',
hidden: true,
},
new_blocker_violations: {
- id: 'AXJMbIl_PAOIsUIE3gtz',
key: 'new_blocker_violations',
type: 'INT',
name: 'New Blocker Issues',
hidden: false,
},
new_bugs: {
- id: 'AXJMbIl_PAOIsUIE3guA',
key: 'new_bugs',
type: 'INT',
name: 'New Bugs',
hidden: false,
},
new_code_smells: {
- id: 'AXJMbIl_PAOIsUIE3gt-',
key: 'new_code_smells',
type: 'INT',
name: 'New Code Smells',
hidden: false,
},
new_critical_violations: {
- id: 'AXJMbIl_PAOIsUIE3gt0',
key: 'new_critical_violations',
type: 'INT',
name: 'New Critical Issues',
hidden: false,
},
new_info_violations: {
- id: 'AXJMbIl_PAOIsUIE3gt3',
key: 'new_info_violations',
type: 'INT',
name: 'New Info Issues',
hidden: false,
},
new_violations: {
- id: 'AXJMbIl_PAOIsUIE3gty',
key: 'new_violations',
type: 'INT',
name: 'New Issues',
hidden: false,
},
new_lines: {
- id: 'AXJMbImPPAOIsUIE3gu2',
key: 'new_lines',
type: 'INT',
name: 'New Lines',
hidden: false,
},
new_major_violations: {
- id: 'AXJMbIl_PAOIsUIE3gt1',
key: 'new_major_violations',
type: 'INT',
name: 'New Major Issues',
hidden: false,
},
new_minor_violations: {
- id: 'AXJMbIl_PAOIsUIE3gt2',
key: 'new_minor_violations',
type: 'INT',
name: 'New Minor Issues',
hidden: false,
},
new_security_hotspots: {
- id: 'AXJMbIl9PAOIsUIE3gsw',
key: 'new_security_hotspots',
type: 'INT',
name: 'New Security Hotspots',
hidden: false,
},
new_vulnerabilities: {
- id: 'AXJMbIl_PAOIsUIE3guC',
key: 'new_vulnerabilities',
type: 'INT',
name: 'New Vulnerabilities',
hidden: false,
},
unanalyzed_c: {
- id: 'AXTb6RMqLLQlB5osv3xN',
key: 'unanalyzed_c',
type: 'INT',
name: 'Number of unanalyzed c files',
hidden: true,
},
unanalyzed_cpp: {
- id: 'AXTb6RMtLLQlB5osv3xO',
key: 'unanalyzed_cpp',
type: 'INT',
name: 'Number of unanalyzed c++ files',
hidden: true,
},
open_issues: {
- id: 'AXJMbIl_PAOIsUIE3gt6',
key: 'open_issues',
type: 'INT',
name: 'Open Issues',
hidden: false,
},
quality_profiles: {
- id: 'AXJMbImPPAOIsUIE3guZ',
key: 'quality_profiles',
type: 'DATA',
name: 'Profiles',
hidden: true,
},
projects: {
- id: 'AXJMbImPPAOIsUIE3guo',
key: 'projects',
type: 'INT',
name: 'Project branches',
hidden: false,
},
public_api: {
- id: 'AXJMbImPPAOIsUIE3gun',
key: 'public_api',
type: 'INT',
name: 'Public API',
hidden: true,
},
public_documented_api_density: {
- id: 'AXJMbImPPAOIsUIE3gur',
key: 'public_documented_api_density',
type: 'PERCENT',
name: 'Public Documented API (%)',
decimalScale: 1,
},
public_undocumented_api: {
- id: 'AXJMbImPPAOIsUIE3gus',
key: 'public_undocumented_api',
type: 'INT',
name: 'Public Undocumented API',
hidden: true,
},
quality_gate_details: {
- id: 'AXJMbImPPAOIsUIE3guY',
key: 'quality_gate_details',
type: 'DATA',
name: 'Quality Gate Details',
hidden: false,
},
alert_status: {
- id: 'AXJMbImPPAOIsUIE3guX',
key: 'alert_status',
type: 'LEVEL',
name: 'Quality Gate Status',
hidden: false,
},
releasability_rating: {
- id: 'AXJMbImPPAOIsUIE3guc',
key: 'releasability_rating',
type: 'RATING',
name: 'Releasability rating',
hidden: false,
},
releasability_rating_distribution: {
- id: 'AX6QkqP7zEziun0YBqmg',
key: 'releasability_rating_distribution',
type: 'DATA',
name: 'Releasability Rating Distribution',
hidden: true,
},
reliability_rating: {
- id: 'AXJMbIl_PAOIsUIE3guP',
key: 'reliability_rating',
type: 'RATING',
name: 'Reliability Rating',
hidden: false,
},
reliability_rating_distribution: {
- id: 'AX6QkqP7zEziun0YBqmi',
key: 'reliability_rating_distribution',
type: 'DATA',
name: 'Reliability Rating Distribution',
hidden: true,
},
new_reliability_rating_distribution: {
- id: 'AX6QkqP8zEziun0YBqmm',
key: 'new_reliability_rating_distribution',
type: 'DATA',
name: 'Reliability Rating Distribution on New Code',
hidden: true,
},
new_reliability_rating: {
- id: 'AXJMbIl_PAOIsUIE3guQ',
key: 'new_reliability_rating',
type: 'RATING',
name: 'Reliability Rating on New Code',
hidden: false,
},
reliability_remediation_effort: {
- id: 'AXJMbIl_PAOIsUIE3guN',
key: 'reliability_remediation_effort',
type: 'WORK_DUR',
name: 'Reliability Remediation Effort',
hidden: false,
},
new_reliability_remediation_effort: {
- id: 'AXJMbIl_PAOIsUIE3guO',
key: 'new_reliability_remediation_effort',
type: 'WORK_DUR',
name: 'Reliability Remediation Effort on New Code',
hidden: false,
},
reopened_issues: {
- id: 'AXJMbIl_PAOIsUIE3gt7',
key: 'reopened_issues',
type: 'INT',
name: 'Reopened Issues',
hidden: false,
},
security_hotspots: {
- id: 'AXJMbIl9PAOIsUIE3gsv',
key: 'security_hotspots',
type: 'INT',
name: 'Security Hotspots',
hidden: false,
},
security_hotspots_reviewed: {
- id: 'AXJMbIl9PAOIsUIE3gs0',
key: 'security_hotspots_reviewed',
type: 'PERCENT',
name: 'Security Hotspots Reviewed',
decimalScale: 1,
},
new_security_hotspots_reviewed: {
- id: 'AXJMbIl9PAOIsUIE3gs1',
key: 'new_security_hotspots_reviewed',
type: 'PERCENT',
name: 'Security Hotspots Reviewed on New Code',
decimalScale: 1,
},
security_rating: {
- id: 'AXJMbIl_PAOIsUIE3guS',
key: 'security_rating',
type: 'RATING',
name: 'Security Rating',
hidden: false,
},
security_rating_distribution: {
- id: 'AX6QkqP7zEziun0YBqmj',
key: 'security_rating_distribution',
type: 'DATA',
name: 'Security Rating Distribution',
hidden: true,
},
new_security_rating_distribution: {
- id: 'AX6QkqP8zEziun0YBqmn',
key: 'new_security_rating_distribution',
type: 'DATA',
name: 'Security Rating Distribution on New Code',
hidden: true,
},
new_security_rating: {
- id: 'AXJMbImPPAOIsUIE3guT',
key: 'new_security_rating',
type: 'RATING',
name: 'Security Rating on New Code',
hidden: false,
},
security_remediation_effort: {
- id: 'AXJMbIl_PAOIsUIE3guG',
key: 'security_remediation_effort',
type: 'WORK_DUR',
name: 'Security Remediation Effort',
hidden: false,
},
new_security_remediation_effort: {
- id: 'AXJMbIl_PAOIsUIE3guR',
key: 'new_security_remediation_effort',
type: 'WORK_DUR',
name: 'Security Remediation Effort on New Code',
hidden: false,
},
security_review_rating: {
- id: 'AXJMbIl9PAOIsUIE3gsx',
key: 'security_review_rating',
type: 'RATING',
name: 'Security Review Rating',
hidden: false,
},
security_review_rating_distribution: {
- id: 'AX6QkqP8zEziun0YBqmk',
key: 'security_review_rating_distribution',
type: 'DATA',
name: 'Security Review Rating Distribution',
hidden: true,
},
new_security_review_rating_distribution: {
- id: 'AX6QkqP8zEziun0YBqmo',
key: 'new_security_review_rating_distribution',
type: 'DATA',
name: 'Security Review Rating Distribution on New Code',
hidden: true,
},
new_security_review_rating: {
- id: 'AXJMbIl9PAOIsUIE3gtA',
key: 'new_security_review_rating',
type: 'RATING',
name: 'Security Review Rating on New Code',
hidden: false,
},
security_hotspots_reviewed_status: {
- id: 'AXJMbIl9PAOIsUIE3gs2',
key: 'security_hotspots_reviewed_status',
type: 'INT',
name: 'Security Review Reviewed Status',
hidden: true,
},
new_security_hotspots_reviewed_status: {
- id: 'AXJMbIl9PAOIsUIE3gtB',
key: 'new_security_hotspots_reviewed_status',
type: 'INT',
name: 'Security Review Reviewed Status on New Code',
hidden: true,
},
security_hotspots_to_review_status: {
- id: 'AXJMbIl9PAOIsUIE3gs3',
key: 'security_hotspots_to_review_status',
type: 'INT',
name: 'Security Review To Review Status',
hidden: true,
},
new_security_hotspots_to_review_status: {
- id: 'AXJMbIl9PAOIsUIE3gs5',
key: 'new_security_hotspots_to_review_status',
type: 'INT',
name: 'Security Review To Review Status on New Code',
hidden: true,
},
skipped_tests: {
- id: 'AXJMbIl9PAOIsUIE3gtd',
key: 'skipped_tests',
type: 'INT',
name: 'Skipped Unit Tests',
hidden: false,
},
statements: {
- id: 'AXJMbImPPAOIsUIE3gum',
key: 'statements',
type: 'INT',
name: 'Statements',
hidden: false,
},
sqale_index: {
- id: 'AXJMbIl_PAOIsUIE3guD',
key: 'sqale_index',
type: 'WORK_DUR',
name: 'Technical Debt',
hidden: false,
},
sqale_debt_ratio: {
- id: 'AXJMbIl_PAOIsUIE3guK',
key: 'sqale_debt_ratio',
type: 'PERCENT',
name: 'Technical Debt Ratio',
decimalScale: 1,
},
new_sqale_debt_ratio: {
- id: 'AXJMbIl_PAOIsUIE3guL',
key: 'new_sqale_debt_ratio',
type: 'PERCENT',
name: 'Technical Debt Ratio on New Code',
decimalScale: 1,
},
maintainability_rating_effort: {
- id: 'AXJMbImPPAOIsUIE3gvD',
key: 'maintainability_rating_effort',
type: 'DATA',
name: 'Total number of projects having worst maintainability rating',
hidden: true,
},
reliability_rating_effort: {
- id: 'AXJMbImPPAOIsUIE3gvC',
key: 'reliability_rating_effort',
type: 'DATA',
name: 'Total number of projects having worst reliability rating',
hidden: true,
},
security_rating_effort: {
- id: 'AXJMbImPPAOIsUIE3gvB',
key: 'security_rating_effort',
type: 'DATA',
name: 'Total number of projects having worst security rating',
hidden: true,
},
security_review_rating_effort: {
- id: 'AXJMbIl9PAOIsUIE3gs6',
key: 'security_review_rating_effort',
type: 'DATA',
name: 'Total number of projects having worst security review rating',
hidden: true,
},
releasability_effort: {
- id: 'AXJMbImPPAOIsUIE3gub',
key: 'releasability_effort',
type: 'INT',
name: 'Total number of projects not production ready',
hidden: true,
},
uncovered_conditions: {
- id: 'AXJMbIl9PAOIsUIE3gs8',
key: 'uncovered_conditions',
type: 'INT',
name: 'Uncovered Conditions',
hidden: false,
},
new_uncovered_conditions: {
- id: 'AXJMbIl9PAOIsUIE3gs9',
key: 'new_uncovered_conditions',
type: 'INT',
name: 'Uncovered Conditions on New Code',
hidden: false,
},
uncovered_lines: {
- id: 'AXJMbIl_PAOIsUIE3gtj',
key: 'uncovered_lines',
type: 'INT',
name: 'Uncovered Lines',
hidden: false,
},
new_uncovered_lines: {
- id: 'AXJMbIl_PAOIsUIE3gtk',
key: 'new_uncovered_lines',
type: 'INT',
name: 'Uncovered Lines on New Code',
hidden: false,
},
test_execution_time: {
- id: 'AXJMbIl9PAOIsUIE3gtb',
key: 'test_execution_time',
type: 'MILLISEC',
name: 'Unit Test Duration',
hidden: false,
},
test_errors: {
- id: 'AXJMbIl9PAOIsUIE3gtc',
key: 'test_errors',
type: 'INT',
name: 'Unit Test Errors',
hidden: false,
},
test_failures: {
- id: 'AXJMbIl9PAOIsUIE3gte',
key: 'test_failures',
type: 'INT',
name: 'Unit Test Failures',
hidden: false,
},
tests: {
- id: 'AXJMbIl9PAOIsUIE3gta',
key: 'tests',
type: 'INT',
name: 'Unit Tests',
hidden: false,
},
test_success_density: {
- id: 'AXJMbIl9PAOIsUIE3gtf',
key: 'test_success_density',
type: 'PERCENT',
name: 'Unit Test Success (%)',
decimalScale: 1,
},
vulnerabilities: {
- id: 'AXJMbIl_PAOIsUIE3guB',
key: 'vulnerabilities',
type: 'INT',
name: 'Vulnerabilities',
hidden: false,
},
accepted_issues: {
- id: 'AXJMbIl_PAOIsUIE3ga7',
key: 'accepted_issues',
type: 'INT',
name: 'Accepted Issues',
const type = overrides.type || MetricType.Percent;
return {
...overrides,
- id: key,
key,
name,
type,
public_api = 'public_api',
public_documented_api_density = 'public_documented_api_density',
public_undocumented_api = 'public_undocumented_api',
- pullrequest_addressed_issues = 'pullrequest_addressed_issues',
+ pull_request_fixed_issues = 'pull_request_fixed_issues',
quality_gate_details = 'quality_gate_details',
quality_profiles = 'quality_profiles',
releasability_effort = 'releasability_effort',
domain?: string;
hidden?: boolean;
higherValuesAreBetter?: boolean;
- id: string;
key: string;
name: string;
qualitative?: boolean;
metric.wont_fix_issues.name=Won't Fix Issues
metric.accepted_issues.description=Accepted issues
metric.accepted_issues.name=Accepted Issues
+metric.pull_request_fixed_issues.name=Fixed issues
+metric.pull_request_fixed_issues.description=Fixed issues
+metric.new_accepted_issues.name=Accepted issues
+metric.new_accepted_issues.description=Accepted issues
#------------------------------------------------------------------------------
#
overview.failed_condition.x_rating_required={rating} is {value}. Required {threshold}
overview.failed_condition.x_required={metric}. Required {threshold}
overview.fix_failed_conditions_with_sonarlint=Fix issues before they fail your Quality Gate with {link} in your IDE. Power up with connected mode!
+overview.pull_request.new_issues=New issues
+overview.pull_request.fixed_issues=Fixed issues
+overview.pull_request.fixed_issues.help=Estimation of issues fixed by this PR
+overview.pull_request.fixed_issues.disclaimer=Only issues fixed on the files modified by the pull request are taken into account. Issues incidentally fixed on unmodified files are not counted.
+overview.pull_request.fixed_issues.disclaimer.2=When the pull request and the target branch are not synchronized, issues introduced on the target branch may be incorrectly considered fixed by the pull request. Rebasing the pull request would give an updated value.
+overview.pull_request.accepted_issues=Accepted issues
+overview.pull_request.accepted_issues.help=Valid issues that were not fixed
overview.quality_gate.status=Quality Gate Status
overview.quality_gate=Quality Gate
overview.quality_gate_x=Quality Gate: {0}
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_gate.required_x=Required {requirement}
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}