aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIsmail Cherri <ismail.cherri@sonarsource.com>2024-11-21 10:18:34 +0100
committersonartech <sonartech@sonarsource.com>2024-11-29 20:03:06 +0000
commit9fd8ce023c13cca6b645a8af40683da66cae43aa (patch)
treec314786dabe225a1ec4a090feb48d923619e234f
parent7aeeaa3fe7b3a491385d467f523a6273a06053fd (diff)
downloadsonarqube-9fd8ce023c13cca6b645a8af40683da66cae43aa.tar.gz
sonarqube-9fd8ce023c13cca6b645a8af40683da66cae43aa.zip
SONAR-23619 Add AI Assurance icon to Quality Gates list
-rw-r--r--server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts39
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/AIAssuredIcon.tsx35
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/CaycCondition.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx39
-rw-r--r--server/sonar-web/src/main/js/design-system/components/icons/ShieldIcon.tsx44
-rw-r--r--server/sonar-web/src/main/js/design-system/components/icons/index.ts1
-rw-r--r--server/sonar-web/src/main/js/types/types.ts1
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties2
11 files changed, 164 insertions, 18 deletions
diff --git a/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts
index d32c988dd19..2413185f8c4 100644
--- a/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts
@@ -202,6 +202,45 @@ export class QualityGatesServiceMock {
caycStatus: CaycStatus.Compliant,
}),
mockQualityGate({
+ name: 'Sonar AI way',
+ conditions: [
+ {
+ id: 'AXJMbIUHPAOIsUIE3eQQ',
+ metric: 'new_violations',
+ op: 'GT',
+ error: '0',
+ isCaycCondition: true,
+ },
+ {
+ id: 'AXJMbIUHPAOIsUIE3eOF',
+ metric: 'new_coverage',
+ op: 'LT',
+ error: '80',
+ isCaycCondition: true,
+ },
+ {
+ id: 'AXJMbIUHPAOIsUIE3eOG',
+ metric: 'new_duplicated_lines_density',
+ op: 'GT',
+ error: '3',
+ isCaycCondition: true,
+ },
+ {
+ id: 'AXJMbIUHPAOIsUIE3eOk',
+ metric: 'new_security_hotspots_reviewed',
+ op: 'LT',
+ error: '100',
+ isCaycCondition: true,
+ },
+ ],
+ isDefault: false,
+ isBuiltIn: true,
+ hasStandardConditions: false,
+ hasMQRConditions: false,
+ isAiCodeSupported: true,
+ caycStatus: CaycStatus.Compliant,
+ }),
+ mockQualityGate({
name: 'Non Cayc QG',
conditions: [
{
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/AIAssuredIcon.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/AIAssuredIcon.tsx
new file mode 100644
index 00000000000..b4cf1d2a233
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/AIAssuredIcon.tsx
@@ -0,0 +1,35 @@
+/*
+ * 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 { ShieldIcon } from '~design-system';
+
+interface Props {
+ className?: string;
+ isDisabled?: boolean;
+}
+
+export default function AIAssuredIcon({ isDisabled = false, className }: Readonly<Props>) {
+ return (
+ <ShieldIcon
+ className={className}
+ fill={isDisabled ? `var(--echoes-color-icon-disabled)` : `var(--echoes-color-icon-accent)`}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx
index 0a1b3363e22..b1c7d6e2de5 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx
@@ -20,6 +20,7 @@
import { withTheme } from '@emotion/react';
import styled from '@emotion/styled';
+import { Spinner } from '@sonarsource/echoes-react';
import { useCallback, useEffect } from 'react';
import { Helmet } from 'react-helmet-async';
import { useIntl } from 'react-intl';
@@ -30,7 +31,6 @@ import {
LAYOUT_GLOBAL_NAV_HEIGHT,
LargeCenteredLayout,
PageContentFontWrapper,
- Spinner,
themeBorder,
themeColor,
} from '~design-system';
@@ -96,7 +96,7 @@ export default function App() {
}}
>
<ListHeader canCreate={canCreate} />
- <Spinner loading={isLoading}>
+ <Spinner isLoading={isLoading}>
<List qualityGates={qualityGates} currentQualityGate={name} />
</Spinner>
</StyledContentWrapper>
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/CaycCondition.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/CaycCondition.tsx
index 190a1b4a575..3741152a19a 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/CaycCondition.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CaycCondition.tsx
@@ -57,7 +57,6 @@ function CaycCondition({ condition, metric, metrics }: Readonly<Props>) {
<StyledContentCell>
<FormattedMessage
id="quality_gates.conditions.cayc.metric"
- defaultMessage={translate('quality_gates.conditions.cayc.metric')}
values={{
metric: getLocalizedMetricNameNoDiffMetric(metric, metrics),
operator: renderOperator(),
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
index 4049712c47e..98801f60680 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
@@ -53,7 +53,7 @@ import {
STANDARD_CONDITIONS_MAP,
} from '../utils';
import AddConditionModal from './AddConditionModal';
-import AIGeneratedIcon from './AIGeneratedIcon';
+import AIAssuredIcon from './AIAssuredIcon';
import CaycCompliantBanner from './CaycCompliantBanner';
import CaycCondition from './CaycCondition';
import CaYCConditionsSimplificationGuide from './CaYCConditionsSimplificationGuide';
@@ -152,7 +152,7 @@ export default function Conditions({ qualityGate, isFetching }: Readonly<Props>)
)}
{isAICodeAssuranceQualityGate && (
<div className="sw-flex sw-items-center sw-mt-2">
- <AIGeneratedIcon className="sw-mr-1" />
+ <AIAssuredIcon className="sw-mr-1" />
<LightLabel>
<FormattedMessage
defaultMessage="quality_gates.ai_generated.description"
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx
index 2d6a5372d62..bbf9a0f700d 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx
@@ -27,7 +27,7 @@ import { getQualityGateUrl } from '../../../helpers/urls';
import { useStandardExperienceModeQuery } from '../../../queries/mode';
import { Feature } from '../../../types/features';
import { CaycStatus, QualityGate } from '../../../types/types';
-import AIGeneratedIcon from './AIGeneratedIcon';
+import AIAssuredIcon from './AIAssuredIcon';
import BuiltInQualityGateBadge from './BuiltInQualityGateBadge';
import QGRecommendedIcon from './QGRecommendedIcon';
@@ -51,12 +51,13 @@ export default function List({ qualityGates, currentQualityGate }: Readonly<Prop
caycStatus,
hasMQRConditions,
hasStandardConditions,
+ isAiCodeSupported,
actions,
}) => {
const isDefaultTitle = isDefault ? ` ${translate('default')}` : '';
const isBuiltInTitle = isBuiltIn ? ` ${translate('quality_gates.built_in')}` : '';
const isAICodeAssuranceQualityGate =
- hasFeature(Feature.AiCodeAssurance) && isBuiltIn && name === 'Sonar way';
+ hasFeature(Feature.AiCodeAssurance) && isAiCodeSupported;
const shouldShowQualityGateUpdateIcon =
actions?.manageConditions === true &&
@@ -106,8 +107,11 @@ export default function List({ qualityGates, currentQualityGate }: Readonly<Prop
content={translate('quality_gates.ai_generated.tooltip.message')}
isOpen={shouldShowQualityGateUpdateIcon ? false : undefined}
>
- <span className="sw-mr-1">
- <AIGeneratedIcon isDisabled={shouldShowQualityGateUpdateIcon} />
+ <span
+ className="sw-mr-1 sw-flex sw-items-start"
+ data-testid="quality-gates-ai-assurance-indicator"
+ >
+ <AIAssuredIcon isDisabled={shouldShowQualityGateUpdateIcon} />
</span>
</Tooltip>
)}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx
index 54a3715fbee..5ddfa705844 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx
@@ -42,6 +42,7 @@ const ui = {
removeCondition: byRole('button', { name: /quality_gates.condition.delete/ }),
listItem: byTestId('js-subnavigation-item'),
requiresUpdateIndicator: byTestId('quality-gates-mqr-standard-mode-update-indicator'),
+ aiAssuranceIndicator: byTestId('quality-gates-ai-assurance-indicator'),
qualityGateListItem: (qualityGateName: string) => byRole('link', { name: qualityGateName }),
newConditionRow: byTestId('quality-gates__conditions-new').byRole('row'),
overallConditionRow: byTestId('quality-gates__conditions-overall').byRole('row'),
@@ -629,6 +630,28 @@ it('should advertise the Sonar way Quality Gate as AI-ready', async () => {
).toBeInTheDocument();
});
+it('should show AI indicator for Sonar AI Way based in DE+ editions', async () => {
+ qualityGateHandler.setIsAdmin(true);
+ renderQualityGateApp({
+ currentUser: mockLoggedInUser(),
+ featureList: [Feature.AiCodeAssurance],
+ });
+
+ expect(await ui.aiAssuranceIndicator.find()).toBeInTheDocument();
+});
+
+it('should not show AI indicator for Sonar AI Way based in Community editions', async () => {
+ qualityGateHandler.setIsAdmin(true);
+ renderQualityGateApp({
+ currentUser: mockLoggedInUser(),
+ featureList: [],
+ });
+
+ await screen.findByText('Sonar way');
+
+ expect(ui.aiAssuranceIndicator.query()).not.toBeInTheDocument();
+});
+
it('should not allow to change value of prioritized_rule_issues', async () => {
const user = userEvent.setup();
qualityGateHandler.setIsAdmin(true);
@@ -936,7 +959,7 @@ describe('Mode transition', () => {
const user = userEvent.setup();
renderQualityGateApp();
- expect(await ui.listItem.findAll()).toHaveLength(9);
+ expect(await ui.listItem.findAll()).toHaveLength(10);
expect(ui.requiresUpdateIndicator.query()).not.toBeInTheDocument();
await user.click(ui.qualityGateListItem('SonarSource way default').get());
expect(byText('quality_gates.cayc.banner.title').get()).toBeInTheDocument();
@@ -950,7 +973,7 @@ describe('Mode transition', () => {
qualityGateHandler.setIsAdmin(true);
renderQualityGateApp();
- expect(await ui.listItem.findAll()).toHaveLength(9);
+ expect(await ui.listItem.findAll()).toHaveLength(10);
expect(
ui.qualityGateListItem('SonarSource way default').by(ui.requiresUpdateIndicator).get(),
).toBeInTheDocument();
@@ -966,7 +989,7 @@ describe('Mode transition', () => {
qualityGateHandler.setIsAdmin(true);
renderQualityGateApp();
- expect(await ui.listItem.findAll()).toHaveLength(9);
+ expect(await ui.listItem.findAll()).toHaveLength(10);
await user.click(ui.qualityGateListItem('SonarSource way default').get());
await user.click(ui.batchUpdate.get());
@@ -1014,7 +1037,7 @@ describe('Mode transition', () => {
qualityGateHandler.setIsAdmin(true);
renderQualityGateApp();
- expect(await ui.listItem.findAll()).toHaveLength(9);
+ expect(await ui.listItem.findAll()).toHaveLength(10);
await user.click(ui.qualityGateListItem('QG without new code conditions').get());
await user.click(await ui.addConditionButton.find());
@@ -1047,7 +1070,7 @@ describe('Mode transition', () => {
const user = userEvent.setup();
renderQualityGateApp();
- expect(await ui.listItem.findAll()).toHaveLength(9);
+ expect(await ui.listItem.findAll()).toHaveLength(10);
expect(ui.requiresUpdateIndicator.query()).not.toBeInTheDocument();
await user.click(ui.qualityGateListItem('QG with MQR conditions').get());
expect(ui.batchUpdate.query()).not.toBeInTheDocument();
@@ -1060,7 +1083,7 @@ describe('Mode transition', () => {
qualityGateHandler.setIsAdmin(true);
renderQualityGateApp();
- expect(await ui.listItem.findAll()).toHaveLength(9);
+ expect(await ui.listItem.findAll()).toHaveLength(10);
expect(
ui.qualityGateListItem('QG with MQR conditions').by(ui.requiresUpdateIndicator).get(),
).toBeInTheDocument();
@@ -1075,7 +1098,7 @@ describe('Mode transition', () => {
qualityGateHandler.setIsAdmin(true);
renderQualityGateApp();
- expect(await ui.listItem.findAll()).toHaveLength(9);
+ expect(await ui.listItem.findAll()).toHaveLength(10);
await user.click(ui.qualityGateListItem('QG with MQR conditions').get());
await user.click(ui.batchUpdate.get());
@@ -1126,7 +1149,7 @@ describe('Mode transition', () => {
qualityGateHandler.setIsAdmin(true);
renderQualityGateApp();
- expect(await ui.listItem.findAll()).toHaveLength(9);
+ expect(await ui.listItem.findAll()).toHaveLength(10);
await user.click(ui.qualityGateListItem('QG with MQR conditions').get());
await user.click(await ui.addConditionButton.find());
diff --git a/server/sonar-web/src/main/js/design-system/components/icons/ShieldIcon.tsx b/server/sonar-web/src/main/js/design-system/components/icons/ShieldIcon.tsx
new file mode 100644
index 00000000000..64727a87b61
--- /dev/null
+++ b/server/sonar-web/src/main/js/design-system/components/icons/ShieldIcon.tsx
@@ -0,0 +1,44 @@
+/*
+ * 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 { useTheme } from '@emotion/react';
+import { themeColor } from '../../helpers/theme';
+import { CustomIcon, IconProps } from './Icon';
+
+export function ShieldIcon({ fill = 'currentColor', ...iconProps }: Readonly<IconProps>) {
+ const theme = useTheme();
+
+ return (
+ <CustomIcon viewBox="0 0 20 20" width={20} height={20} {...iconProps}>
+ <g fill={themeColor(fill)({ theme })}>
+ <path
+ fillRule="evenodd"
+ clipRule="evenodd"
+ d="M7.78736 9.00555C7.7045 9.06668 7.62013 9.12592 7.53432 9.1832C7.14912 9.44032 6.7348 9.658 6.29688 9.83071C6.7348 10.0034 7.14912 10.2211 7.53432 10.4782C7.62013 10.5355 7.7045 10.5947 7.78736 10.6559C8.31646 11.0462 8.78415 11.5139 9.17451 12.043C9.23564 12.1259 9.29488 12.2102 9.35216 12.2961C9.60928 12.6813 9.82696 13.0956 9.99967 13.5335C10.1724 13.0956 10.3901 12.6813 10.6472 12.2961C10.7045 12.2102 10.7637 12.1259 10.8248 12.043C11.2152 11.5139 11.6829 11.0462 12.212 10.6559C12.2949 10.5947 12.3792 10.5355 12.465 10.4782C12.8502 10.2211 13.2646 10.0034 13.7025 9.83071C13.2646 9.658 12.8502 9.44032 12.465 9.1832C12.3792 9.12592 12.2949 9.06668 12.212 9.00555C11.6829 8.61519 11.2152 8.14751 10.8248 7.6184C10.7637 7.53555 10.7045 7.45118 10.6472 7.36537C10.3901 6.98017 10.1724 6.56585 9.99967 6.12793C9.82696 6.56585 9.60928 6.98017 9.35216 7.36537C9.29488 7.45118 9.23564 7.53555 9.17451 7.61841C8.78415 8.14751 8.31646 8.61519 7.78736 9.00555ZM8.98407 9.83071C9.35249 10.1379 9.69243 10.4779 9.99968 10.8463C10.3069 10.4779 10.6469 10.1379 11.0153 9.83071C10.6469 9.52347 10.3069 9.18353 9.99967 8.81511C9.69243 9.18353 9.35249 9.52347 8.98407 9.83071Z"
+ fill={themeColor(fill)({ theme })}
+ />
+ <path
+ d="M9.99973 18.1777C8.06916 17.6916 6.47539 16.5839 5.21844 14.8548C3.96148 13.1256 3.33301 11.2055 3.33301 9.09434V4.01099L9.99973 1.51099L16.6664 4.01099V9.09434C16.6664 11.2055 16.038 13.1256 14.781 14.8548C13.5241 16.5839 11.9303 17.6916 9.99973 18.1777ZM9.99973 16.4277C11.4442 15.9694 12.6386 15.0527 13.5831 13.6777C14.5275 12.3027 14.9998 10.7749 14.9998 9.09434V5.15683L9.99973 3.28182L4.99969 5.15683V9.09434C4.99969 10.7749 5.47191 12.3027 6.41636 13.6777C7.36082 15.0527 8.55527 15.9694 9.99973 16.4277Z"
+ fill={themeColor(fill)({ theme })}
+ />
+ </g>
+ </CustomIcon>
+ );
+}
diff --git a/server/sonar-web/src/main/js/design-system/components/icons/index.ts b/server/sonar-web/src/main/js/design-system/components/icons/index.ts
index 45d0a0c4ff9..7ae75fb0318 100644
--- a/server/sonar-web/src/main/js/design-system/components/icons/index.ts
+++ b/server/sonar-web/src/main/js/design-system/components/icons/index.ts
@@ -77,6 +77,7 @@ export { SeverityCriticalIcon } from './SeverityCriticalIcon';
export { SeverityInfoIcon } from './SeverityInfoIcon';
export { SeverityMajorIcon } from './SeverityMajorIcon';
export { SeverityMinorIcon } from './SeverityMinorIcon';
+export { ShieldIcon } from './ShieldIcon';
export { SnoozeCircleIcon } from './SnoozeCircleIcon';
export { SoftwareImpactSeverityBlockerIcon } from './SoftwareImpactSeverityBlockerIcon';
export { SoftwareImpactSeverityHighIcon } from './SoftwareImpactSeverityHighIcon';
diff --git a/server/sonar-web/src/main/js/types/types.ts b/server/sonar-web/src/main/js/types/types.ts
index f0af608905f..42698c8c3d5 100644
--- a/server/sonar-web/src/main/js/types/types.ts
+++ b/server/sonar-web/src/main/js/types/types.ts
@@ -485,6 +485,7 @@ export interface QualityGate extends QualityGatePreview {
conditions?: Condition[];
hasMQRConditions?: boolean;
hasStandardConditions?: boolean;
+ isAiCodeSupported?: boolean;
isBuiltIn?: boolean;
}
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 4bf3975a842..787f05c92d9 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -2598,7 +2598,7 @@ quality_gates.cayc.banner.description2=It ensures that:
quality_gates.cayc_unfollow.description=You may click unlock to edit this quality gate. Adding extra conditions to a compliant quality gate can result in drawbacks. Are you reconsidering {cayc_link}? We strongly recommend this methodology to achieve a Clean Code status.
quality_gates.cayc.review_update_modal.add_condition.header= {0} condition(s) on new code will be added
quality_gates.cayc.review_update_modal.modify_condition.header= {0} condition(s) on new code will be modified
-quality_gates.ai_generated.tooltip.message=Sonar way ensures clean AI-generated code
+quality_gates.ai_generated.tooltip.message=This quality gate is qualified for AI Code Assurance
quality_gates.ai_generated.description=Sonar way ensures {link}
quality_gates.ai_generated.description.clean_ai_generated_code=clean AI-generated code
quality_gates.mqr_mode_update.tooltip.message=Update the metrics of this quality gate