]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20607 Onboarding (#9553)
authorPhilippe Perrin <philippe.perrin@sonarsource.com>
Wed, 11 Oct 2023 07:11:15 +0000 (09:11 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 12 Oct 2023 20:02:51 +0000 (20:02 +0000)
server/sonar-web/src/main/js/api/mocks/UsersServiceMock.ts
server/sonar-web/src/main/js/apps/quality-gates/components/CaYCConditionsSimplificationGuide.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/components/CaycCondition.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx
server/sonar-web/src/main/js/types/users.ts
server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/CurrentActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/DismissNoticeActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/CurrentAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DismissNoticeAction.java
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 3c76abf76b0ab221f8cd12b973ef270efdc15bcb..878ad25b9b23a949f24d8890808df323c323cd54 100644 (file)
@@ -30,6 +30,7 @@ import {
   UserGroup,
   changePassword,
   deleteUser,
+  dismissNotice,
   getIdentityProviders,
   getUserGroups,
   getUsers,
@@ -139,6 +140,7 @@ export default class UsersServiceMock {
     jest.mocked(removeUserFromGroup).mockImplementation(this.handleRemoveUserFromGroup);
     jest.mocked(changePassword).mockImplementation(this.handleChangePassword);
     jest.mocked(deleteUser).mockImplementation(this.handleDeactivateUser);
+    jest.mocked(dismissNotice).mockResolvedValue({});
   }
 
   setIsManaged(managed: boolean) {
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/CaYCConditionsSimplificationGuide.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/CaYCConditionsSimplificationGuide.tsx
new file mode 100644 (file)
index 0000000..b5cc148
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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 { SpotlightTour, SpotlightTourStep } from 'design-system';
+import React from 'react';
+import { dismissNotice } from '../../../api/users';
+import { CurrentUserContext } from '../../../app/components/current-user/CurrentUserContext';
+import { translate } from '../../../helpers/l10n';
+import { NoticeType } from '../../../types/users';
+
+export default function CaYCConditionsSimplificationGuide() {
+  const { currentUser, updateDismissedNotices } = React.useContext(CurrentUserContext);
+  const shouldRun =
+    currentUser.isLoggedIn &&
+    !currentUser.dismissedNotices[NoticeType.QG_CAYC_CONDITIONS_SIMPLIFICATION];
+
+  const steps: SpotlightTourStep[] = [
+    {
+      target: '[data-guiding-id="caycConditionsSimplification"]',
+      content: (
+        <>
+          <p className="sw-mb-4">
+            {translate('quality_gates.cayc.condition_simplification_tour.content1')}
+          </p>
+          <p>{translate('quality_gates.cayc.condition_simplification_tour.content2')}</p>
+        </>
+      ),
+      title: translate('quality_gates.cayc.condition_simplification_tour.title'),
+      placement: 'right',
+    },
+  ];
+
+  const onCallback = async (props: { action: string }) => {
+    if (props.action === 'close' && shouldRun) {
+      await dismissNotice(NoticeType.QG_CAYC_CONDITIONS_SIMPLIFICATION);
+      updateDismissedNotices(NoticeType.QG_CAYC_CONDITIONS_SIMPLIFICATION, true);
+    }
+  };
+
+  return (
+    <SpotlightTour
+      run={shouldRun}
+      closeLabel={translate('dismiss')}
+      callback={onCallback}
+      steps={steps}
+    />
+  );
+}
index fdaeb30dbf89f6f1f4c07f36eee3fdfc7fc37581..552805278b3935a50116f5445a0c08b902763df3 100644 (file)
@@ -25,6 +25,7 @@ import withMetricsContext from '../../../app/components/metrics/withMetricsConte
 import DocumentationTooltip from '../../../components/common/DocumentationTooltip';
 import { translate } from '../../../helpers/l10n';
 import { formatMeasure } from '../../../helpers/measures';
+import { MetricKey } from '../../../types/metrics';
 import { Condition, Dict, Metric } from '../../../types/types';
 import { getCaycConditionMetadata, getLocalizedMetricNameNoDiffMetric } from '../utils';
 
@@ -44,7 +45,11 @@ function CaycCondition({ condition, metric, metrics }: Readonly<Props>) {
 
   return (
     <TableRow>
-      <ContentCell>
+      <ContentCell
+        data-guiding-id={
+          condition.metric === MetricKey.new_violations ? 'caycConditionsSimplification' : undefined
+        }
+      >
         <Highlight>{translate(`metric.${metric.key}.description.positive`)}</Highlight>
       </ContentCell>
 
index 38a5fd864c0aaf434f8efc29f577aec938927cb2..c435559384e6315778ddc5d2cfc868d70262f22f 100644 (file)
@@ -52,6 +52,7 @@ import {
   QualityGate,
 } from '../../../types/types';
 import { groupAndSortByPriorityConditions } from '../utils';
+import CaYCConditionsSimplificationGuide from './CaYCConditionsSimplificationGuide';
 import CaycConditionsTable from './CaycConditionsTable';
 import ConditionModal from './ConditionModal';
 import CaycReviewUpdateConditionsModal from './ConditionReviewAndUpdateModal';
@@ -169,6 +170,8 @@ export function Conditions({
 
   return (
     <div>
+      <CaYCConditionsSimplificationGuide />
+
       {qualityGate.caycStatus === CaycStatus.NonCompliant && canEdit && (
         <CardWithPrimaryBackground className="sw-mb-9 sw-p-8">
           <Title as="h2" className="sw-mb-2 sw-heading-md">
index 521a7ec83d6b862a812d14c0380290eba4f0a134..e7f1c489ead5fe13de89a2c9577bdeb838663dab 100644 (file)
@@ -21,23 +21,33 @@ import { act, screen, waitFor, within } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import selectEvent from 'react-select-event';
 import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock';
+import UsersServiceMock from '../../../../api/mocks/UsersServiceMock';
 import { searchProjects, searchUsers } from '../../../../api/quality-gates';
+import { dismissNotice } from '../../../../api/users';
+import { mockLoggedInUser } from '../../../../helpers/testMocks';
 import { RenderContext, renderAppRoutes } from '../../../../helpers/testReactTestingUtils';
+import { byRole } from '../../../../helpers/testSelector';
 import { Feature } from '../../../../types/features';
+import { NoticeType } from '../../../../types/users';
 import routes from '../../routes';
 
-let handler: QualityGatesServiceMock;
+let qualityGateHandler: QualityGatesServiceMock;
+let usersHandler: UsersServiceMock;
 
 beforeAll(() => {
-  handler = new QualityGatesServiceMock();
+  qualityGateHandler = new QualityGatesServiceMock();
+  usersHandler = new UsersServiceMock();
 });
 
-afterEach(() => handler.reset());
+afterEach(() => {
+  qualityGateHandler.reset();
+  usersHandler.reset();
+});
 
 it('should open the default quality gates', async () => {
   renderQualityGateApp();
 
-  const defaultQualityGate = handler.getDefaultQualityGate();
+  const defaultQualityGate = qualityGateHandler.getDefaultQualityGate();
   expect(
     await screen.findByRole('button', {
       current: 'page',
@@ -51,13 +61,13 @@ it('should list all quality gates', async () => {
 
   expect(
     await screen.findByRole('button', {
-      name: `${handler.getDefaultQualityGate().name} default`,
+      name: `${qualityGateHandler.getDefaultQualityGate().name} default`,
     }),
   ).toBeInTheDocument();
 
   expect(
     screen.getByRole('button', {
-      name: `${handler.getBuiltInQualityGate().name} quality_gates.built_in`,
+      name: `${qualityGateHandler.getBuiltInQualityGate().name} quality_gates.built_in`,
     }),
   ).toBeInTheDocument();
 });
@@ -77,7 +87,7 @@ it('should render the built-in quality gate properly', async () => {
 
 it('should be able to create a quality gate then delete it', async () => {
   const user = userEvent.setup();
-  handler.setIsAdmin(true);
+  qualityGateHandler.setIsAdmin(true);
   renderQualityGateApp();
   let createButton = await screen.findByRole('button', { name: 'create' });
 
@@ -124,7 +134,7 @@ it('should be able to create a quality gate then delete it', async () => {
 
 it('should be able to copy a quality gate which is CAYC compliant', async () => {
   const user = userEvent.setup();
-  handler.setIsAdmin(true);
+  qualityGateHandler.setIsAdmin(true);
   renderQualityGateApp();
 
   const notDefaultQualityGate = await screen.findByText('Sonar way');
@@ -144,7 +154,7 @@ it('should be able to copy a quality gate which is CAYC compliant', async () =>
 
 it('should not be able to copy a quality gate which is not CAYC compliant', async () => {
   const user = userEvent.setup();
-  handler.setIsAdmin(true);
+  qualityGateHandler.setIsAdmin(true);
   renderQualityGateApp();
 
   const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily');
@@ -157,7 +167,7 @@ it('should not be able to copy a quality gate which is not CAYC compliant', asyn
 
 it('should be able to rename a quality gate', async () => {
   const user = userEvent.setup();
-  handler.setIsAdmin(true);
+  qualityGateHandler.setIsAdmin(true);
   renderQualityGateApp();
   await user.click(await screen.findByLabelText('menu'));
   const renameButton = screen.getByRole('menuitem', { name: 'rename' });
@@ -173,7 +183,7 @@ it('should be able to rename a quality gate', async () => {
 
 it('should not be able to set as default a quality gate which is not CAYC compliant', async () => {
   const user = userEvent.setup();
-  handler.setIsAdmin(true);
+  qualityGateHandler.setIsAdmin(true);
   renderQualityGateApp();
 
   const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily');
@@ -185,7 +195,7 @@ it('should not be able to set as default a quality gate which is not CAYC compli
 
 it('should be able to set as default a quality gate which is CAYC compliant', async () => {
   const user = userEvent.setup();
-  handler.setIsAdmin(true);
+  qualityGateHandler.setIsAdmin(true);
   renderQualityGateApp();
 
   const notDefaultQualityGate = await screen.findByRole('button', { name: /Sonar way/ });
@@ -198,7 +208,7 @@ it('should be able to set as default a quality gate which is CAYC compliant', as
 
 it('should be able to add a condition', async () => {
   const user = userEvent.setup();
-  handler.setIsAdmin(true);
+  qualityGateHandler.setIsAdmin(true);
   renderQualityGateApp();
 
   await user.click(await screen.findByText('SonarSource way - CFamily'));
@@ -253,7 +263,7 @@ it('should be able to add a condition', async () => {
 
 it('should be able to edit a condition', async () => {
   const user = userEvent.setup();
-  handler.setIsAdmin(true);
+  qualityGateHandler.setIsAdmin(true);
   renderQualityGateApp();
 
   const newConditions = within(await screen.findByTestId('quality-gates__conditions-new'));
@@ -276,12 +286,14 @@ it('should be able to edit a condition', async () => {
 
 it('should be able to handle duplicate or deprecated condition', async () => {
   const user = userEvent.setup();
-  handler.setIsAdmin(true);
+  qualityGateHandler.setIsAdmin(true);
   renderQualityGateApp();
 
   await user.click(
     // make it a regexp to ignore badges:
-    await screen.findByRole('button', { name: new RegExp(handler.getCorruptedQualityGateName()) }),
+    await screen.findByRole('button', {
+      name: new RegExp(qualityGateHandler.getCorruptedQualityGateName()),
+    }),
   );
 
   expect(await screen.findByText('quality_gates.duplicated_conditions')).toBeInTheDocument();
@@ -292,7 +304,7 @@ it('should be able to handle duplicate or deprecated condition', async () => {
 
 it('should be able to handle delete condition', async () => {
   const user = userEvent.setup();
-  handler.setIsAdmin(true);
+  qualityGateHandler.setIsAdmin(true);
   renderQualityGateApp();
 
   await user.click(await screen.findByText('Non Cayc QG'));
@@ -323,7 +335,7 @@ it('should explain condition on branch', async () => {
 
 it('should show warning banner when CAYC condition is not properly set and should be able to update them', async () => {
   const user = userEvent.setup();
-  handler.setIsAdmin(true);
+  qualityGateHandler.setIsAdmin(true);
   renderQualityGateApp();
 
   const qualityGate = await screen.findByText('SonarSource way - CFamily');
@@ -392,7 +404,7 @@ it('should not warn user when quality gate is not CAYC compliant and user has no
 
 it('should warn user when quality gate is not CAYC compliant and user has permission to edit it', async () => {
   const user = userEvent.setup();
-  handler.setIsAdmin(true);
+  qualityGateHandler.setIsAdmin(true);
   renderQualityGateApp();
 
   const nonCompliantQualityGate = await screen.findByRole('button', { name: /Non Cayc QG/ });
@@ -404,17 +416,56 @@ it('should warn user when quality gate is not CAYC compliant and user has permis
 });
 
 it('should render CaYC conditions on a separate table', async () => {
-  handler.setIsAdmin(true);
+  qualityGateHandler.setIsAdmin(true);
   renderQualityGateApp();
 
   expect(await screen.findByTestId('quality-gates__conditions-cayc')).toBeInTheDocument();
   expect(await screen.findByTestId('quality-gates__conditions-new')).toBeInTheDocument();
 });
 
+it('should display CaYC condition simplification tour for users who didnt dismissed it', async () => {
+  const user = userEvent.setup();
+  qualityGateHandler.setIsAdmin(true);
+  renderQualityGateApp({ currentUser: mockLoggedInUser() });
+
+  const qualityGate = await screen.findByText('SonarSource way');
+
+  await act(async () => {
+    await user.click(qualityGate);
+  });
+
+  expect(await byRole('alertdialog').find()).toBeInTheDocument();
+
+  await act(async () => {
+    await user.click(byRole('alertdialog').byRole('button', { name: 'dismiss' }).get());
+  });
+
+  expect(await byRole('alertdialog').query()).not.toBeInTheDocument();
+  expect(dismissNotice).toHaveBeenLastCalledWith(NoticeType.QG_CAYC_CONDITIONS_SIMPLIFICATION);
+});
+
+it('should not display CaYC condition simplification tour for users who dismissed it', async () => {
+  const user = userEvent.setup();
+  qualityGateHandler.setIsAdmin(true);
+  renderQualityGateApp({
+    currentUser: mockLoggedInUser({
+      dismissedNotices: { [NoticeType.QG_CAYC_CONDITIONS_SIMPLIFICATION]: true },
+    }),
+  });
+
+  const qualityGate = await screen.findByText('SonarSource way');
+
+  await act(async () => {
+    await user.click(qualityGate);
+  });
+
+  expect(await byRole('alertdialog').query()).not.toBeInTheDocument();
+});
+
 describe('The Project section', () => {
   it('should render list of projects correctly in different tabs', async () => {
     const user = userEvent.setup();
-    handler.setIsAdmin(true);
+    qualityGateHandler.setIsAdmin(true);
     renderQualityGateApp();
 
     const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily');
@@ -435,7 +486,7 @@ describe('The Project section', () => {
 
   it('should handle select and deselect correctly', async () => {
     const user = userEvent.setup();
-    handler.setIsAdmin(true);
+    qualityGateHandler.setIsAdmin(true);
     renderQualityGateApp();
 
     const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily');
@@ -466,7 +517,7 @@ describe('The Project section', () => {
 
   it('should handle the search of projects', async () => {
     const user = userEvent.setup();
-    handler.setIsAdmin(true);
+    qualityGateHandler.setIsAdmin(true);
     renderQualityGateApp();
 
     const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily');
@@ -490,7 +541,7 @@ describe('The Project section', () => {
     });
 
     const user = userEvent.setup();
-    handler.setIsAdmin(true);
+    qualityGateHandler.setIsAdmin(true);
     renderQualityGateApp();
 
     const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily');
@@ -507,14 +558,14 @@ describe('The Permissions section', () => {
     // await just to make sure we've loaded the page
     expect(
       await screen.findByRole('button', {
-        name: `${handler.getDefaultQualityGate().name} default`,
+        name: `${qualityGateHandler.getDefaultQualityGate().name} default`,
       }),
     ).toBeInTheDocument();
 
     expect(screen.queryByText('quality_gates.permissions')).not.toBeInTheDocument();
   });
   it('should show button to grant permission when user is admin', async () => {
-    handler.setIsAdmin(true);
+    qualityGateHandler.setIsAdmin(true);
     renderQualityGateApp();
 
     const grantPermissionButton = await screen.findByRole('button', {
@@ -526,7 +577,7 @@ describe('The Permissions section', () => {
 
   it('should assign permission to a user and delete it later', async () => {
     const user = userEvent.setup();
-    handler.setIsAdmin(true);
+    qualityGateHandler.setIsAdmin(true);
     renderQualityGateApp();
 
     expect(screen.queryByText('userlogin')).not.toBeInTheDocument();
@@ -575,7 +626,7 @@ describe('The Permissions section', () => {
 
   it('should assign permission to a group and delete it later', async () => {
     const user = userEvent.setup();
-    handler.setIsAdmin(true);
+    qualityGateHandler.setIsAdmin(true);
     renderQualityGateApp();
 
     expect(screen.queryByText('userlogin')).not.toBeInTheDocument();
@@ -611,7 +662,7 @@ describe('The Permissions section', () => {
     (searchUsers as jest.Mock).mockRejectedValue('error');
 
     const user = userEvent.setup();
-    handler.setIsAdmin(true);
+    qualityGateHandler.setIsAdmin(true);
     renderQualityGateApp();
 
     const grantPermissionButton = await screen.findByRole('button', {
index 9ee0ffdf38922e2693cc3fae2bfba01668c5cfc8..33ec00a96e1ec1e7e1ffa96496e751df967ea794 100644 (file)
@@ -33,6 +33,7 @@ export enum NoticeType {
   EDUCATION_PRINCIPLES = 'educationPrinciples',
   SONARLINT_AD = 'sonarlintAd',
   ISSUE_GUIDE = 'issueCleanCodeGuide',
+  QG_CAYC_CONDITIONS_SIMPLIFICATION = 'qualityGateCaYCConditionsSimplification',
 }
 
 export interface LoggedInUser extends CurrentUser, UserActive {
index 0bbf9174777c37edea536024b54575ef019e437d..39449c1bb12fd33f40ac3552f723ebf98ce54b00 100644 (file)
  */
 package org.sonar.server.user.ws;
 
+import java.util.Collection;
 import java.util.Map;
 import org.assertj.core.groups.Tuple;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Suite;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.resources.ResourceType;
 import org.sonar.api.resources.ResourceTypeTree;
@@ -51,238 +55,247 @@ import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFIL
 import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
 import static org.sonar.db.permission.GlobalPermission.SCAN;
 import static org.sonar.db.user.GroupTesting.newGroupDto;
+import static org.sonar.server.user.ws.DismissNoticeAction.AVAILABLE_NOTICE_KEYS;
 import static org.sonar.test.JsonAssert.assertJson;
 
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+  CurrentActionIT.OtherTest.class,
+  CurrentActionIT.DimissableNoticeTest.class
+})
 public class CurrentActionIT {
-  @Rule
-  public UserSessionRule userSession = UserSessionRule.standalone();
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-
-  private final PlatformEditionProvider platformEditionProvider = mock(PlatformEditionProvider.class);
-  private final HomepageTypesImpl homepageTypes = new HomepageTypesImpl();
-  private final PermissionService permissionService = new PermissionServiceImpl(new ResourceTypes(new ResourceTypeTree[] {
-    ResourceTypeTree.builder().addType(ResourceType.builder(Qualifiers.PROJECT).build()).build()}));
-  private final WsActionTester ws = new WsActionTester(
-    new CurrentAction(userSession, db.getDbClient(), new AvatarResolverImpl(), homepageTypes, platformEditionProvider, permissionService));
-
-  @Test
-  public void return_user_info() {
-    UserDto user = db.users().insertUser(u -> u
-      .setLogin("obiwan.kenobi")
-      .setName("Obiwan Kenobi")
-      .setEmail("obiwan.kenobi@starwars.com")
-      .setLocal(true)
-      .setExternalLogin("obiwan")
-      .setExternalIdentityProvider("sonarqube")
-      .setScmAccounts(newArrayList("obiwan:github", "obiwan:bitbucket")));
-    userSession.logIn(user);
-
-    CurrentWsResponse response = call();
-
-    assertThat(response)
-      .extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::getEmail, CurrentWsResponse::getAvatar,
-        CurrentWsResponse::getLocal,
-        CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider, CurrentWsResponse::getScmAccountsList)
-      .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", "obiwan.kenobi@starwars.com", "f5aa64437a1821ffe8b563099d506aef", true, "obiwan", "sonarqube",
-        newArrayList("obiwan:bitbucket", "obiwan:github"));
+  public static class OtherTest {
+    @Rule
+    public UserSessionRule userSession = UserSessionRule.standalone();
+    @Rule
+    public DbTester db = DbTester.create(System2.INSTANCE);
+
+    private final PlatformEditionProvider platformEditionProvider = mock(PlatformEditionProvider.class);
+    private final HomepageTypesImpl homepageTypes = new HomepageTypesImpl();
+    private final PermissionService permissionService = new PermissionServiceImpl(new ResourceTypes(new ResourceTypeTree[] {
+      ResourceTypeTree.builder().addType(ResourceType.builder(Qualifiers.PROJECT).build()).build()}));
+    private final WsActionTester ws = new WsActionTester(
+      new CurrentAction(userSession, db.getDbClient(), new AvatarResolverImpl(), homepageTypes, platformEditionProvider, permissionService));
+
+    private CurrentWsResponse call() {
+      return ws.newRequest().executeProtobuf(CurrentWsResponse.class);
+    }
+
+    @Test
+    public void return_user_info() {
+      UserDto user = db.users().insertUser(u -> u
+        .setLogin("obiwan.kenobi")
+        .setName("Obiwan Kenobi")
+        .setEmail("obiwan.kenobi@starwars.com")
+        .setLocal(true)
+        .setExternalLogin("obiwan")
+        .setExternalIdentityProvider("sonarqube")
+        .setScmAccounts(newArrayList("obiwan:github", "obiwan:bitbucket")));
+      userSession.logIn(user);
+
+      CurrentWsResponse response = call();
+
+      assertThat(response)
+        .extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::getEmail, CurrentWsResponse::getAvatar,
+          CurrentWsResponse::getLocal,
+          CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider, CurrentWsResponse::getScmAccountsList)
+        .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", "obiwan.kenobi@starwars.com", "f5aa64437a1821ffe8b563099d506aef", true, "obiwan", "sonarqube",
+          newArrayList("obiwan:bitbucket", "obiwan:github"));
+    }
+
+    @Test
+    public void return_minimal_user_info() {
+      UserDto user = db.users().insertUser(u -> u
+        .setLogin("obiwan.kenobi")
+        .setName("Obiwan Kenobi")
+        .setEmail(null)
+        .setLocal(true)
+        .setExternalLogin("obiwan")
+        .setExternalIdentityProvider("sonarqube")
+        .setScmAccounts(emptyList()));
+      userSession.logIn(user);
+
+      CurrentWsResponse response = call();
+
+      assertThat(response)
+        .extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::hasAvatar, CurrentWsResponse::getLocal,
+          CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider, CurrentWsResponse::getUsingSonarLintConnectedMode)
+        .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", false, true, "obiwan", "sonarqube", false);
+      assertThat(response.hasEmail()).isFalse();
+      assertThat(response.getScmAccountsList()).isEmpty();
+      assertThat(response.getGroupsList()).isEmpty();
+      assertThat(response.getPermissions().getGlobalList()).isEmpty();
+    }
+
+    @Test
+    public void convert_empty_email_to_null() {
+      UserDto user = db.users().insertUser(u -> u
+        .setLogin("obiwan.kenobi")
+        .setEmail(""));
+      userSession.logIn(user);
+
+      CurrentWsResponse response = call();
+
+      assertThat(response.hasEmail()).isFalse();
+    }
+
+    @Test
+    public void return_group_membership() {
+      UserDto user = db.users().insertUser();
+      userSession.logIn(user);
+      db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Jedi")), user);
+      db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Rebel")), user);
+
+      CurrentWsResponse response = call();
+
+      assertThat(response.getGroupsList()).containsOnly("Jedi", "Rebel");
+    }
+
+    @Test
+    public void return_permissions() {
+      UserDto user = db.users().insertUser();
+      userSession
+        .logIn(user)
+        .addPermission(SCAN)
+        .addPermission(ADMINISTER_QUALITY_PROFILES);
+
+      CurrentWsResponse response = call();
+      assertThat(response.getPermissions().getGlobalList()).containsOnly("profileadmin", "scan");
+    }
+
+    @Test
+    public void fail_with_ISE_when_user_login_in_db_does_not_exist() {
+      db.users().insertUser(usert -> usert.setLogin("another"));
+      userSession.logIn("obiwan.kenobi");
+
+      assertThatThrownBy(this::call)
+        .isInstanceOf(IllegalStateException.class)
+        .hasMessage("User login 'obiwan.kenobi' cannot be found");
+    }
+
+    @Test
+    public void anonymous() {
+      userSession
+        .anonymous()
+        .addPermission(SCAN)
+        .addPermission(PROVISION_PROJECTS);
+
+      CurrentWsResponse response = call();
+
+      assertThat(response.getIsLoggedIn()).isFalse();
+      assertThat(response.getPermissions().getGlobalList()).containsOnly("scan", "provisioning");
+      assertThat(response)
+        .extracting(CurrentWsResponse::hasLogin, CurrentWsResponse::hasName, CurrentWsResponse::hasEmail, CurrentWsResponse::hasLocal,
+          CurrentWsResponse::hasExternalIdentity, CurrentWsResponse::hasExternalProvider)
+        .containsOnly(false);
+      assertThat(response.getScmAccountsList()).isEmpty();
+      assertThat(response.getGroupsList()).isEmpty();
+    }
+
+    @Test
+    public void json_example() {
+      ComponentDto componentDto = db.components().insertPrivateProject(u -> u.setUuid("UUID-of-the-death-star").setKey("death-star-key")).getMainBranchComponent();
+      UserDto obiwan = db.users().insertUser(user -> user
+        .setLogin("obiwan.kenobi")
+        .setName("Obiwan Kenobi")
+        .setEmail("obiwan.kenobi@starwars.com")
+        .setLocal(true)
+        .setExternalLogin("obiwan.kenobi")
+        .setExternalIdentityProvider("sonarqube")
+        .setScmAccounts(newArrayList("obiwan:github", "obiwan:bitbucket"))
+        .setHomepageType("PROJECT")
+        .setHomepageParameter("UUID-of-the-death-star"));
+      userSession
+        .logIn(obiwan)
+        .addPermission(SCAN)
+        .addPermission(ADMINISTER_QUALITY_PROFILES)
+        .addProjectPermission(USER, db.components().getProjectDtoByMainBranch(componentDto));
+      db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Jedi")), obiwan);
+      db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Rebel")), obiwan);
+
+      String response = ws.newRequest().execute().getInput();
+
+      assertJson(response).isSimilarTo(getClass().getResource("current-example.json"));
+    }
+
+    @Test
+    public void handle_givenSonarLintUserInDatabase_returnSonarLintUserFromTheEndpoint() {
+      UserDto user = db.users().insertUser(u -> u.setLastSonarlintConnectionDate(System.currentTimeMillis()));
+      userSession.logIn(user);
+
+      CurrentWsResponse response = call();
+
+      assertThat(response.getUsingSonarLintConnectedMode()).isTrue();
+    }
+
+    @Test
+    public void test_definition() {
+      WebService.Action definition = ws.getDef();
+      assertThat(definition.key()).isEqualTo("current");
+      assertThat(definition.description()).isEqualTo("Get the details of the current authenticated user.");
+      assertThat(definition.since()).isEqualTo("5.2");
+      assertThat(definition.isPost()).isFalse();
+      assertThat(definition.isInternal()).isTrue();
+      assertThat(definition.responseExampleAsString()).isNotEmpty();
+      assertThat(definition.params()).isEmpty();
+      assertThat(definition.changelog()).isNotEmpty();
+    }
   }
 
-  @Test
-  public void return_educationPrinciples_dismiss_notice() {
-    UserDto user = db.users().insertUser();
-    userSession.logIn(user);
-
-    PropertyDto property = new PropertyDto().setUserUuid(user.getUuid()).setKey("user.dismissedNotices.educationPrinciples");
-    db.properties().insertProperties(userSession.getLogin(), null, null, null, property);
-
-    CurrentWsResponse response = call();
-
-    assertThat(response.getDismissedNoticesMap().entrySet())
-      .extracting(Map.Entry::getKey, Map.Entry::getValue)
-      .contains(Tuple.tuple("educationPrinciples", true));
-  }
-
-  @Test
-  public void return_educationPrinciples_not_dismissed() {
-    UserDto user = db.users().insertUser();
-    userSession.logIn(user);
-
-    CurrentWsResponse response = call();
-
-    assertThat(response.getDismissedNoticesMap().entrySet())
-      .extracting(Map.Entry::getKey, Map.Entry::getValue)
-      .contains(Tuple.tuple("educationPrinciples", false));
-  }
-
-  @Test
-  public void return_minimal_user_info() {
-    UserDto user = db.users().insertUser(u -> u
-      .setLogin("obiwan.kenobi")
-      .setName("Obiwan Kenobi")
-      .setEmail(null)
-      .setLocal(true)
-      .setExternalLogin("obiwan")
-      .setExternalIdentityProvider("sonarqube")
-      .setScmAccounts(emptyList()));
-    userSession.logIn(user);
-
-    CurrentWsResponse response = call();
-
-    assertThat(response)
-      .extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::hasAvatar, CurrentWsResponse::getLocal,
-        CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider, CurrentWsResponse::getUsingSonarLintConnectedMode)
-      .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", false, true, "obiwan", "sonarqube", false);
-    assertThat(response.hasEmail()).isFalse();
-    assertThat(response.getScmAccountsList()).isEmpty();
-    assertThat(response.getGroupsList()).isEmpty();
-    assertThat(response.getPermissions().getGlobalList()).isEmpty();
-  }
-
-  @Test
-  public void convert_empty_email_to_null() {
-    UserDto user = db.users().insertUser(u -> u
-      .setLogin("obiwan.kenobi")
-      .setEmail(""));
-    userSession.logIn(user);
-
-    CurrentWsResponse response = call();
-
-    assertThat(response.hasEmail()).isFalse();
-  }
-
-  @Test
-  public void return_group_membership() {
-    UserDto user = db.users().insertUser();
-    userSession.logIn(user);
-    db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Jedi")), user);
-    db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Rebel")), user);
-
-    CurrentWsResponse response = call();
-
-    assertThat(response.getGroupsList()).containsOnly("Jedi", "Rebel");
-  }
-
-  @Test
-  public void return_permissions() {
-    UserDto user = db.users().insertUser();
-    userSession
-      .logIn(user)
-      .addPermission(SCAN)
-      .addPermission(ADMINISTER_QUALITY_PROFILES);
-
-    CurrentWsResponse response = call();
-    assertThat(response.getPermissions().getGlobalList()).containsOnly("profileadmin", "scan");
-  }
-
-  @Test
-  public void fail_with_ISE_when_user_login_in_db_does_not_exist() {
-    db.users().insertUser(usert -> usert.setLogin("another"));
-    userSession.logIn("obiwan.kenobi");
-
-    assertThatThrownBy(this::call)
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("User login 'obiwan.kenobi' cannot be found");
+  @RunWith(Parameterized.class)
+  public static class DimissableNoticeTest {
+    @Rule
+    public UserSessionRule userSession = UserSessionRule.standalone();
+    @Rule
+    public DbTester db = DbTester.create(System2.INSTANCE);
+
+    private final PlatformEditionProvider platformEditionProvider = mock(PlatformEditionProvider.class);
+    private final HomepageTypesImpl homepageTypes = new HomepageTypesImpl();
+    private final PermissionService permissionService = new PermissionServiceImpl(new ResourceTypes(new ResourceTypeTree[] {
+      ResourceTypeTree.builder().addType(ResourceType.builder(Qualifiers.PROJECT).build()).build()}));
+    private final WsActionTester ws = new WsActionTester(
+      new CurrentAction(userSession, db.getDbClient(), new AvatarResolverImpl(), homepageTypes, platformEditionProvider, permissionService));
+
+    private CurrentWsResponse call() {
+      return ws.newRequest().executeProtobuf(CurrentWsResponse.class);
+    }
+
+    @Parameterized.Parameters
+    public static Collection<String> parameterCombination() {
+      return AVAILABLE_NOTICE_KEYS;
+    }
+
+    private final String notice;
+
+    public DimissableNoticeTest(String notice) {
+      this.notice = notice;
+    }
+
+    @Test
+    public void return_dismissed_notice() {
+      UserDto user = db.users().insertUser();
+      userSession.logIn(user);
+
+      PropertyDto property = new PropertyDto().setUserUuid(user.getUuid()).setKey("user.dismissedNotices." + this.notice);
+      db.properties().insertProperties(userSession.getLogin(), null, null, null, property);
+
+      CurrentWsResponse response = call();
+
+      assertThat(response.getDismissedNoticesMap().entrySet())
+        .extracting(Map.Entry::getKey, Map.Entry::getValue)
+        .contains(Tuple.tuple(this.notice, true));
+    }
+
+    @Test
+    public void return_not_dismissed_notice() {
+      UserDto user = db.users().insertUser();
+      userSession.logIn(user);
+
+      CurrentWsResponse response = call();
+
+      assertThat(response.getDismissedNoticesMap().entrySet())
+        .extracting(Map.Entry::getKey, Map.Entry::getValue)
+        .contains(Tuple.tuple(this.notice, false));
+    }
   }
-
-  @Test
-  public void anonymous() {
-    userSession
-      .anonymous()
-      .addPermission(SCAN)
-      .addPermission(PROVISION_PROJECTS);
-
-    CurrentWsResponse response = call();
-
-    assertThat(response.getIsLoggedIn()).isFalse();
-    assertThat(response.getPermissions().getGlobalList()).containsOnly("scan", "provisioning");
-    assertThat(response)
-      .extracting(CurrentWsResponse::hasLogin, CurrentWsResponse::hasName, CurrentWsResponse::hasEmail, CurrentWsResponse::hasLocal,
-        CurrentWsResponse::hasExternalIdentity, CurrentWsResponse::hasExternalProvider)
-      .containsOnly(false);
-    assertThat(response.getScmAccountsList()).isEmpty();
-    assertThat(response.getGroupsList()).isEmpty();
-  }
-
-  @Test
-  public void json_example() {
-    ComponentDto componentDto = db.components().insertPrivateProject(u -> u.setUuid("UUID-of-the-death-star").setKey("death-star-key")).getMainBranchComponent();
-    UserDto obiwan = db.users().insertUser(user -> user
-      .setLogin("obiwan.kenobi")
-      .setName("Obiwan Kenobi")
-      .setEmail("obiwan.kenobi@starwars.com")
-      .setLocal(true)
-      .setExternalLogin("obiwan.kenobi")
-      .setExternalIdentityProvider("sonarqube")
-      .setScmAccounts(newArrayList("obiwan:github", "obiwan:bitbucket"))
-      .setHomepageType("PROJECT")
-      .setHomepageParameter("UUID-of-the-death-star"));
-    userSession
-      .logIn(obiwan)
-      .addPermission(SCAN)
-      .addPermission(ADMINISTER_QUALITY_PROFILES)
-      .addProjectPermission(USER, db.components().getProjectDtoByMainBranch(componentDto));
-    db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Jedi")), obiwan);
-    db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Rebel")), obiwan);
-
-    String response = ws.newRequest().execute().getInput();
-
-    assertJson(response).isSimilarTo(getClass().getResource("current-example.json"));
-  }
-
-  @Test
-  public void handle_givenSonarLintUserInDatabase_returnSonarLintUserFromTheEndpoint() {
-    UserDto user = db.users().insertUser(u -> u.setLastSonarlintConnectionDate(System.currentTimeMillis()));
-    userSession.logIn(user);
-
-    CurrentWsResponse response = call();
-
-    assertThat(response.getUsingSonarLintConnectedMode()).isTrue();
-  }
-
-  @Test
-  public void return_sonarlintAd_dismiss_notice() {
-    UserDto user = db.users().insertUser();
-    userSession.logIn(user);
-
-    PropertyDto property = new PropertyDto().setUserUuid(user.getUuid()).setKey("user.dismissedNotices.sonarlintAd");
-    db.properties().insertProperties(userSession.getLogin(), null, null, null, property);
-
-    CurrentWsResponse response = call();
-
-    assertThat(response.getDismissedNoticesMap().entrySet())
-      .extracting(Map.Entry::getKey, Map.Entry::getValue)
-      .contains(Tuple.tuple("sonarlintAd", true));
-  }
-
-  @Test
-  public void return_sonarlintAd_not_dismissed() {
-    UserDto user = db.users().insertUser();
-    userSession.logIn(user);
-
-    CurrentWsResponse response = call();
-
-    assertThat(response.getDismissedNoticesMap().entrySet())
-      .extracting(Map.Entry::getKey, Map.Entry::getValue)
-      .contains(Tuple.tuple("sonarlintAd", false));
-  }
-
-
-  @Test
-  public void test_definition() {
-    WebService.Action definition = ws.getDef();
-    assertThat(definition.key()).isEqualTo("current");
-    assertThat(definition.description()).isEqualTo("Get the details of the current authenticated user.");
-    assertThat(definition.since()).isEqualTo("5.2");
-    assertThat(definition.isPost()).isFalse();
-    assertThat(definition.isInternal()).isTrue();
-    assertThat(definition.responseExampleAsString()).isNotEmpty();
-    assertThat(definition.params()).isEmpty();
-    assertThat(definition.changelog()).isNotEmpty();
-  }
-
-  private CurrentWsResponse call() {
-    return ws.newRequest().executeProtobuf(CurrentWsResponse.class);
-  }
-
 }
index 401b803165f8781009ce3e4925aa420129a9a87f..68ba21b934b6ef3923a7499f53715be83dcbe284 100644 (file)
@@ -85,7 +85,6 @@ public class DismissNoticeActionIT {
     assertThat(propertyDto).isPresent();
   }
 
-
   @Test
   public void authentication_is_required() {
     TestRequest testRequest = tester.newRequest()
@@ -96,7 +95,6 @@ public class DismissNoticeActionIT {
       .hasMessage("Authentication is required");
   }
 
-
   @Test
   public void notice_parameter_is_mandatory() {
     userSessionRule.logIn();
@@ -107,7 +105,6 @@ public class DismissNoticeActionIT {
       .hasMessage("The 'notice' parameter is missing");
   }
 
-
   @Test
   public void notice_not_supported() {
     userSessionRule.logIn();
@@ -116,10 +113,10 @@ public class DismissNoticeActionIT {
 
     assertThatThrownBy(testRequest::execute)
       .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("Value of parameter 'notice' (not_supported_value) must be one of: [educationPrinciples, sonarlintAd, issueCleanCodeGuide]");
+      .hasMessage(
+        "Value of parameter 'notice' (not_supported_value) must be one of: [educationPrinciples, sonarlintAd, issueCleanCodeGuide, qualityGateCaYCConditionsSimplification]");
   }
 
-
   @Test
   public void notice_already_exist_dont_fail() {
     userSessionRule.logIn();
@@ -135,5 +132,4 @@ public class DismissNoticeActionIT {
     assertThat(db.properties().findFirstUserProperty(userSessionRule.getUuid(), "user.dismissedNotices.educationPrinciples")).isPresent();
   }
 
-
 }
index 8db6b9768fb65556f9cdbcaf7002fb568744881d..9d458c7f590d2352aba93949858cfa4497d1474b 100644 (file)
@@ -49,15 +49,13 @@ import static java.util.Optional.of;
 import static java.util.Optional.ofNullable;
 import static org.apache.commons.lang.StringUtils.EMPTY;
 import static org.sonar.api.web.UserRole.USER;
-import static org.sonar.server.user.ws.DismissNoticeAction.EDUCATION_PRINCIPLES;
-import static org.sonar.server.user.ws.DismissNoticeAction.ISSUE_CLEAN_CODE_GUIDE;
-import static org.sonar.server.user.ws.DismissNoticeAction.SONARLINT_AD;
+import static org.sonar.server.user.ws.DismissNoticeAction.AVAILABLE_NOTICE_KEYS;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.Users.CurrentWsResponse.Permissions;
+import static org.sonarqube.ws.Users.CurrentWsResponse.newBuilder;
 import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.APPLICATION;
 import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.PORTFOLIO;
 import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.PROJECT;
-import static org.sonarqube.ws.Users.CurrentWsResponse.Permissions;
-import static org.sonarqube.ws.Users.CurrentWsResponse.newBuilder;
 import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_CURRENT;
 
 public class CurrentAction implements UsersWsAction {
@@ -124,10 +122,10 @@ public class CurrentAction implements UsersWsAction {
       .addAllScmAccounts(user.getSortedScmAccounts())
       .setPermissions(Permissions.newBuilder().addAllGlobal(getGlobalPermissions()).build())
       .setHomepage(buildHomepage(dbSession, user))
-      .setUsingSonarLintConnectedMode(user.getLastSonarlintConnectionDate() != null)
-      .putDismissedNotices(EDUCATION_PRINCIPLES, isNoticeDismissed(user, EDUCATION_PRINCIPLES))
-      .putDismissedNotices(SONARLINT_AD, isNoticeDismissed(user, SONARLINT_AD))
-      .putDismissedNotices(ISSUE_CLEAN_CODE_GUIDE, isNoticeDismissed(user, ISSUE_CLEAN_CODE_GUIDE));
+      .setUsingSonarLintConnectedMode(user.getLastSonarlintConnectionDate() != null);
+
+    AVAILABLE_NOTICE_KEYS.forEach(key -> builder.putDismissedNotices(key, isNoticeDismissed(user, key)));
+    
     ofNullable(emptyToNull(user.getEmail())).ifPresent(builder::setEmail);
     ofNullable(emptyToNull(user.getEmail())).ifPresent(u -> builder.setAvatar(avatarResolver.create(user)));
     ofNullable(user.getExternalLogin()).ifPresent(builder::setExternalIdentity);
index f1d2e432ddf2453ae35095c8cd805da85789ec95..352c599e537a7cb265d21d30e440e1278562d4a2 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.user.ws;
 
+import java.util.List;
 import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
@@ -33,9 +34,12 @@ import static com.google.common.base.Preconditions.checkState;
 
 public class DismissNoticeAction implements UsersWsAction {
 
-  public static final String EDUCATION_PRINCIPLES = "educationPrinciples";
-  public static final String SONARLINT_AD = "sonarlintAd";
-  public static final String ISSUE_CLEAN_CODE_GUIDE = "issueCleanCodeGuide";
+  private static final String EDUCATION_PRINCIPLES = "educationPrinciples";
+  private static final String SONARLINT_AD = "sonarlintAd";
+  private static final String ISSUE_CLEAN_CODE_GUIDE = "issueCleanCodeGuide";
+  private static final String QUALITY_GATE_CAYC_CONDITIONS_SIMPLIFICATION = "qualityGateCaYCConditionsSimplification";
+
+  protected static final List<String> AVAILABLE_NOTICE_KEYS = List.of(EDUCATION_PRINCIPLES, SONARLINT_AD, ISSUE_CLEAN_CODE_GUIDE, QUALITY_GATE_CAYC_CONDITIONS_SIMPLIFICATION);
   public static final String USER_DISMISS_CONSTANT = "user.dismissedNotices.";
 
   private final UserSession userSession;
@@ -51,6 +55,7 @@ public class DismissNoticeAction implements UsersWsAction {
     WebService.NewAction action = context.createAction("dismiss_notice")
       .setDescription("Dismiss a notice for the current user. Silently ignore if the notice is already dismissed.")
       .setChangelog(new Change("10.2", "Support for new notice '%s' was added.".formatted(ISSUE_CLEAN_CODE_GUIDE)))
+      .setChangelog(new Change("10.3", "Support for new notice '%s' was added.".formatted(QUALITY_GATE_CAYC_CONDITIONS_SIMPLIFICATION)))
       .setSince("9.6")
       .setInternal(true)
       .setHandler(this)
@@ -59,7 +64,7 @@ public class DismissNoticeAction implements UsersWsAction {
     action.createParam("notice")
       .setDescription("notice key to dismiss")
       .setExampleValue(EDUCATION_PRINCIPLES)
-      .setPossibleValues(EDUCATION_PRINCIPLES, SONARLINT_AD, ISSUE_CLEAN_CODE_GUIDE);
+      .setPossibleValues(AVAILABLE_NOTICE_KEYS);
   }
 
   @Override
index 657664d9cd6997ca4b8fd56c24d22c606bf257de..30ad2ed6fdcdcbf947f6c6353e29fc5ce1d0813f 100644 (file)
@@ -2214,6 +2214,9 @@ quality_gates.cayc.review_update_modal.header=Fix "{0}" to comply with Clean as
 quality_gates.cayc.review_update_modal.confirm_text=Fix Quality Gate
 quality_gates.cayc.review_update_modal.description1=This quality gate will be updated to comply with {cayc_link}. Please review the changes below.
 quality_gates.cayc.review_update_modal.description2=All other conditions will be preserved
+quality_gates.cayc.condition_simplification_tour.title=One condition, zero issues
+quality_gates.cayc.condition_simplification_tour.content1=One single condition ensures that new code has 0 issues.
+quality_gates.cayc.condition_simplification_tour.content2=This condition replaces the three conditions on Security rating, Reliability rating and Maintainability rating.
 quality_gates.cayc.new_maintainability_rating.A=Technical debt ratio is less than {0}
 quality_gates.cayc.new_maintainability_rating=Technical debt ratio is greater than {1}
 quality_gates.cayc.new_reliability_rating.A=No bugs