]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18259 Drop deprecated params from QG endpoints
authorstanislavh <stanislav.honcharov@sonarsource.com>
Tue, 31 Jan 2023 09:59:08 +0000 (10:59 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 1 Feb 2023 20:02:56 +0000 (20:02 +0000)
20 files changed:
server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts
server/sonar-web/src/main/js/api/quality-gates.ts
server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateApp.tsx
server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateAppRenderer.tsx
server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-it.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateAppRenderer-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/ProjectQualityGateApp-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/ProjectQualityGateAppRenderer-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/Projects.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissions.tsx
server/sonar-web/src/main/js/apps/quality-gates/utils.ts
server/sonar-web/src/main/js/helpers/mocks/quality-gates.ts
server/sonar-web/src/main/js/types/types.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 0fedec86c3674794eebe587f84d3104dee4e31e7..269875da4b671f4f2a9eead035fa809a2b7ef287 100644 (file)
@@ -36,6 +36,7 @@ import {
   dissociateGateWithProject,
   fetchQualityGate,
   fetchQualityGates,
+  getGateForProject,
   renameQualityGate,
   searchGroups,
   searchProjects,
@@ -49,11 +50,12 @@ export class QualityGatesServiceMock {
   readOnlyList: QualityGate[];
   list: QualityGate[];
   projects: Project[];
+  getGateForProjectGateName: string;
+  throwOnGetGateForProject: boolean;
 
-  constructor(list?: QualityGate[], defaultId = 'AWBWEMe2qGAMGEYPjJlm') {
+  constructor(list?: QualityGate[]) {
     this.readOnlyList = list || [
       mockQualityGate({
-        id: defaultId,
         name: 'SonarSource way',
         conditions: [
           { id: 'AXJMbIUGPAOIsUIE3eNC', metric: 'new_coverage', op: 'LT', error: '85' },
@@ -85,7 +87,6 @@ export class QualityGatesServiceMock {
         isCaycCompliant: true,
       }),
       mockQualityGate({
-        id: 'AXGYZrDqC-YjVCvvbRDY',
         name: 'SonarSource way - CFamily',
         conditions: [
           { id: 'AXJMbIUHPAOIsUIE3eOu', metric: 'new_coverage', op: 'LT', error: '0' },
@@ -96,7 +97,6 @@ export class QualityGatesServiceMock {
         isBuiltIn: false,
       }),
       mockQualityGate({
-        id: 'AWBWEMe4qGAMGEYPjJlr',
         name: 'Sonar way',
         conditions: [
           { id: 'AXJMbIUHPAOIsUIE3eNs', metric: 'new_security_rating', op: 'GT', error: '1' },
@@ -126,7 +126,6 @@ export class QualityGatesServiceMock {
         isCaycCompliant: true,
       }),
       mockQualityGate({
-        id: 'AWBWEMe4qGAMGEYPjJlruit',
         name: 'Non Cayc QG',
         conditions: [
           { id: 'AXJMbIUHPAOIsUIE3eNs', metric: 'new_security_rating', op: 'GT', error: '1' },
@@ -137,6 +136,22 @@ export class QualityGatesServiceMock {
         isBuiltIn: false,
         isCaycCompliant: false,
       }),
+      mockQualityGate({
+        name: 'QG without conditions',
+        conditions: [],
+        isDefault: false,
+        isBuiltIn: false,
+        isCaycCompliant: false,
+      }),
+      mockQualityGate({
+        name: 'QG without new code conditions',
+        conditions: [
+          { id: 'AXJMbIUHPAOIsUIE3eNs', metric: 'security_rating', op: 'GT', error: '1' },
+        ],
+        isDefault: false,
+        isBuiltIn: false,
+        isCaycCompliant: false,
+      }),
     ];
 
     this.list = cloneDeep(this.readOnlyList);
@@ -148,6 +163,9 @@ export class QualityGatesServiceMock {
       { key: 'test4', name: 'test4', selected: true },
     ];
 
+    this.getGateForProjectGateName = 'SonarSource way';
+    this.throwOnGetGateForProject = false;
+
     (fetchQualityGate as jest.Mock).mockImplementation(this.showHandler);
     (fetchQualityGates as jest.Mock).mockImplementation(this.listHandler);
     (createQualityGate as jest.Mock).mockImplementation(this.createHandler);
@@ -163,6 +181,7 @@ export class QualityGatesServiceMock {
     (associateGateWithProject as jest.Mock).mockImplementation(this.selectHandler);
     (dissociateGateWithProject as jest.Mock).mockImplementation(this.deSelectHandler);
     (setQualityGateAsDefault as jest.Mock).mockImplementation(this.setDefaultHandler);
+    (getGateForProject as jest.Mock).mockImplementation(this.projectGateHandler);
 
     // To be implemented.
     (addUser as jest.Mock).mockResolvedValue({});
@@ -176,6 +195,7 @@ export class QualityGatesServiceMock {
   reset() {
     this.setIsAdmin(false);
     this.list = cloneDeep(this.readOnlyList);
+    this.getGateForProjectGateName = 'SonarSource way';
   }
 
   getDefaultQualityGate() {
@@ -190,6 +210,14 @@ export class QualityGatesServiceMock {
     this.isAdmin = isAdmin;
   }
 
+  setGetGateForProjectName(name: string) {
+    this.getGateForProjectGateName = name;
+  }
+
+  setThrowOnGetGateForProject(value: boolean) {
+    this.throwOnGetGateForProject = value;
+  }
+
   computeActions(q: QualityGate) {
     return {
       rename: q.isBuiltIn ? false : this.isAdmin,
@@ -210,7 +238,7 @@ export class QualityGatesServiceMock {
           ...q,
           actions: this.computeActions(q),
         })),
-      default: this.getDefaultQualityGate().id,
+      default: this.getDefaultQualityGate().name,
       actions: { create: this.isAdmin },
     });
   };
@@ -224,10 +252,8 @@ export class QualityGatesServiceMock {
   };
 
   createHandler = ({ name }: { name: string }) => {
-    const newId = `newId${this.list.length}`;
     this.list.push(
       mockQualityGate({
-        id: newId,
         name,
         conditions: [
           mockCondition({
@@ -257,7 +283,6 @@ export class QualityGatesServiceMock {
       })
     );
     return this.reply({
-      id: newId,
       name,
     });
   };
@@ -275,7 +300,6 @@ export class QualityGatesServiceMock {
       });
     }
     newQG.name = name;
-    newQG.id = `newId${this.list.length}`;
 
     newQG.isDefault = false;
     newQG.isBuiltIn = false;
@@ -283,7 +307,6 @@ export class QualityGatesServiceMock {
     this.list.push(newQG);
 
     return this.reply({
-      id: newQG.id,
       name,
     });
   };
@@ -297,7 +320,6 @@ export class QualityGatesServiceMock {
     }
     renameQG.name = name;
     return this.reply({
-      id: renameQG.id,
       name,
     });
   };
@@ -318,20 +340,21 @@ export class QualityGatesServiceMock {
 
   createConditionHandler = (
     data: {
-      gateId: string;
+      gateName: string;
     } & Omit<Condition, 'id'>
   ) => {
-    const { metric, gateId, op, error } = data;
-    const qg = this.list.find((q) => q.id === gateId);
+    const { metric, gateName, op, error } = data;
+    const qg = this.list.find((q) => q.name === gateName);
     if (qg === undefined) {
       return Promise.reject({
-        errors: [{ msg: `No quality gate has been found for id ${gateId}` }],
+        errors: [{ msg: `No quality gate has been found for name ${gateName}` }],
       });
     }
 
     const conditions = qg.conditions || [];
-    const id = `condId${qg.id}${conditions.length}`;
-    const newCondition = { id, metric, op, error };
+    const id = `condId${qg.name}${conditions.length}`;
+    const newCondition = { metric, op, error, id };
+
     conditions.push(newCondition);
     qg.conditions = conditions;
     return this.reply(newCondition);
@@ -414,6 +437,14 @@ export class QualityGatesServiceMock {
     return Promise.resolve();
   };
 
+  projectGateHandler = () => {
+    if (this.throwOnGetGateForProject) {
+      return Promise.reject('unknown');
+    }
+
+    return this.reply(this.list.find((qg) => qg.name === this.getGateForProjectGateName));
+  };
+
   reply<T>(response: T): Promise<T> {
     return Promise.resolve(cloneDeep(response));
   }
index 513cdd06599452a111c22cbb6573ec1e1866d3f7..2a3c7a4bf694453d7f58b31f8bcede8e75e4c723 100644 (file)
@@ -67,7 +67,7 @@ export function setQualityGateAsDefault(data: { name: string }): Promise<void |
 
 export function createCondition(
   data: {
-    gateId: string;
+    gateName: string;
   } & Omit<Condition, 'id'>
 ): Promise<Condition> {
   return postJSON('/api/qualitygates/create_condition', data).catch(throwGlobalError);
@@ -106,7 +106,7 @@ export function searchProjects(data: {
 }
 
 export function associateGateWithProject(data: {
-  gateId: string;
+  gateName: string;
   projectKey: string;
 }): Promise<void | Response> {
   return post('/api/qualitygates/select', data).catch(throwGlobalError);
index 3af87aaae6caae139f3d61cab08343b4a683a9fe..3365b99d6dc6a1b3e2dc51e9ae7bf18bec248c93 100644 (file)
@@ -28,7 +28,10 @@ import {
 } from '../../api/quality-gates';
 import withComponentContext from '../../app/components/componentContext/withComponentContext';
 import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization';
-import { addGlobalSuccessMessage } from '../../helpers/globalMessages';
+import {
+  addGlobalErrorMessageFromAPI,
+  addGlobalSuccessMessage,
+} from '../../helpers/globalMessages';
 import { translate } from '../../helpers/l10n';
 import { Component, QualityGate } from '../../types/types';
 import { USE_SYSTEM_DEFAULT } from './constants';
@@ -43,15 +46,15 @@ interface State {
   allQualityGates?: QualityGate[];
   currentQualityGate?: QualityGate;
   loading: boolean;
-  selectedQualityGateId: string;
+  selectedQualityGateName: string;
   submitting: boolean;
 }
 
-export class ProjectQualityGateApp extends React.PureComponent<Props, State> {
+class ProjectQualityGateApp extends React.PureComponent<Props, State> {
   mounted = false;
   state: State = {
     loading: true,
-    selectedQualityGateId: USE_SYSTEM_DEFAULT,
+    selectedQualityGateName: USE_SYSTEM_DEFAULT,
     submitting: false,
   };
 
@@ -113,7 +116,10 @@ export class ProjectQualityGateApp extends React.PureComponent<Props, State> {
     const [allQualityGates, currentQualityGate] = await Promise.all([
       this.fetchDetailedQualityGates(),
       getGateForProject({ project: component.key }),
-    ]).catch(() => []);
+    ]).catch((error) => {
+      addGlobalErrorMessageFromAPI(error);
+      return [];
+    });
 
     if (allQualityGates && currentQualityGate) {
       const usingDefault = await this.isUsingDefault(currentQualityGate);
@@ -122,7 +128,7 @@ export class ProjectQualityGateApp extends React.PureComponent<Props, State> {
         this.setState({
           allQualityGates,
           currentQualityGate,
-          selectedQualityGateId: usingDefault ? USE_SYSTEM_DEFAULT : currentQualityGate.id,
+          selectedQualityGateName: usingDefault ? USE_SYSTEM_DEFAULT : currentQualityGate.name,
           loading: false,
         });
       }
@@ -131,13 +137,13 @@ export class ProjectQualityGateApp extends React.PureComponent<Props, State> {
     }
   };
 
-  handleSelect = (selectedQualityGateId: string) => {
-    this.setState({ selectedQualityGateId });
+  handleSelect = (selectedQualityGateName: string) => {
+    this.setState({ selectedQualityGateName });
   };
 
   handleSubmit = async () => {
     const { component } = this.props;
-    const { allQualityGates, currentQualityGate, selectedQualityGateId } = this.state;
+    const { allQualityGates, currentQualityGate, selectedQualityGateName } = this.state;
 
     if (allQualityGates === undefined || currentQualityGate === undefined) {
       return;
@@ -145,7 +151,7 @@ export class ProjectQualityGateApp extends React.PureComponent<Props, State> {
 
     this.setState({ submitting: true });
 
-    if (selectedQualityGateId === USE_SYSTEM_DEFAULT) {
+    if (selectedQualityGateName === USE_SYSTEM_DEFAULT) {
       await dissociateGateWithProject({
         projectKey: component.key,
       }).catch(() => {
@@ -153,7 +159,7 @@ export class ProjectQualityGateApp extends React.PureComponent<Props, State> {
       });
     } else {
       await associateGateWithProject({
-        gateId: selectedQualityGateId,
+        gateName: selectedQualityGateName,
         projectKey: component.key,
       }).catch(() => {
         /* noop */
@@ -164,9 +170,9 @@ export class ProjectQualityGateApp extends React.PureComponent<Props, State> {
       addGlobalSuccessMessage(translate('project_quality_gate.successfully_updated'));
 
       const newGate =
-        selectedQualityGateId === USE_SYSTEM_DEFAULT
+        selectedQualityGateName === USE_SYSTEM_DEFAULT
           ? allQualityGates.find((gate) => gate.isDefault)
-          : allQualityGates.find((gate) => gate.id === selectedQualityGateId);
+          : allQualityGates.find((gate) => gate.name === selectedQualityGateName);
 
       if (newGate) {
         this.setState({ currentQualityGate: newGate, submitting: false });
@@ -180,7 +186,7 @@ export class ProjectQualityGateApp extends React.PureComponent<Props, State> {
       return null;
     }
 
-    const { allQualityGates, currentQualityGate, loading, selectedQualityGateId, submitting } =
+    const { allQualityGates, currentQualityGate, loading, selectedQualityGateName, submitting } =
       this.state;
 
     return (
@@ -190,7 +196,7 @@ export class ProjectQualityGateApp extends React.PureComponent<Props, State> {
         loading={loading}
         onSubmit={this.handleSubmit}
         onSelect={this.handleSelect}
-        selectedQualityGateId={selectedQualityGateId}
+        selectedQualityGateName={selectedQualityGateName}
         submitting={submitting}
       />
     );
index 809bc392819e03c72a6d6f37ddb2da5b94a24dd5..d177ea7a804cdddf33646d77ab400c2315d6c211 100644 (file)
@@ -43,7 +43,7 @@ export interface ProjectQualityGateAppRendererProps {
   loading: boolean;
   onSelect: (id: string) => void;
   onSubmit: () => void;
-  selectedQualityGateId: string;
+  selectedQualityGateName: string;
   submitting: boolean;
 }
 
@@ -83,7 +83,8 @@ function renderQualitygateOption(props: OptionProps<QualityGateOption, false>) {
 }
 
 export default function ProjectQualityGateAppRenderer(props: ProjectQualityGateAppRendererProps) {
-  const { allQualityGates, currentQualityGate, loading, selectedQualityGateId, submitting } = props;
+  const { allQualityGates, currentQualityGate, loading, selectedQualityGateName, submitting } =
+    props;
   const defaultQualityGate = allQualityGates?.find((g) => g.isDefault);
 
   if (loading) {
@@ -98,19 +99,19 @@ export default function ProjectQualityGateAppRenderer(props: ProjectQualityGateA
     return null;
   }
 
-  const usesDefault = selectedQualityGateId === USE_SYSTEM_DEFAULT;
+  const usesDefault = selectedQualityGateName === USE_SYSTEM_DEFAULT;
   const needsReanalysis = usesDefault
     ? // currentQualityGate.isDefault is not always up to date. We need to check
       // against defaultQualityGate explicitly.
-      defaultQualityGate.id !== currentQualityGate.id
-    : selectedQualityGateId !== currentQualityGate.id;
+      defaultQualityGate.name !== currentQualityGate.name
+    : selectedQualityGateName !== currentQualityGate.name;
 
-  const selectedQualityGate = allQualityGates.find((qg) => qg.id === selectedQualityGateId);
+  const selectedQualityGate = allQualityGates.find((qg) => qg.name === selectedQualityGateName);
 
   const options: QualityGateOption[] = allQualityGates.map((g) => ({
     isDisabled: g.conditions === undefined || g.conditions.length === 0,
     label: g.name,
-    value: g.id,
+    value: g.name,
   }));
 
   return (
@@ -180,7 +181,7 @@ export default function ProjectQualityGateAppRenderer(props: ProjectQualityGateA
                   props.onSelect(value);
                 }
               }}
-              value={!usesDefault ? selectedQualityGateId : currentQualityGate.id}
+              value={!usesDefault ? selectedQualityGateName : currentQualityGate.name}
             >
               <div className="spacer-left">
                 <div className="little-spacer-bottom">
@@ -197,8 +198,9 @@ export default function ProjectQualityGateAppRenderer(props: ProjectQualityGateA
                     onChange={({ value }: QualityGateOption) => {
                       props.onSelect(value);
                     }}
+                    aria-label={translate('project_quality_gate.select_specific_qg')}
                     options={options}
-                    value={options.find((o) => o.value === selectedQualityGateId)}
+                    value={options.find((o) => o.value === selectedQualityGateName)}
                   />
                 </div>
               </div>
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-it.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-it.tsx
new file mode 100644 (file)
index 0000000..847fb98
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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 userEvent from '@testing-library/user-event';
+import selectEvent from 'react-select-event';
+import { byRole, byText } from 'testing-library-selector';
+import { QualityGatesServiceMock } from '../../../api/mocks/QualityGatesServiceMock';
+import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization';
+import { mockComponent } from '../../../helpers/mocks/component';
+import {
+  renderAppWithComponentContext,
+  RenderContext,
+} from '../../../helpers/testReactTestingUtils';
+import { Component } from '../../../types/types';
+import routes from '../routes';
+
+jest.mock('../../../api/quality-gates');
+
+jest.mock('../../../app/utils/handleRequiredAuthorization');
+
+let handler: QualityGatesServiceMock;
+
+const ui = {
+  qualityGateHeading: byRole('heading', { name: 'project_quality_gate.page' }),
+  defaultRadioQualityGate: byRole('radio', {
+    name: /project_quality_gate.always_use_default/,
+  }),
+  specificRadioQualityGate: byRole('radio', { name: /project_quality_gate.always_use_specific/ }),
+  qualityGatesSelect: byRole('textbox', { name: 'project_quality_gate.select_specific_qg' }),
+  QGWithoutConditionsOptionLabel: byRole('radio', {
+    name: /option QG without conditions selected/,
+  }),
+  QGWithoutNewCodeConditionOptionLabel: byRole('radio', {
+    name: 'project_quality_gate.always_use_specific QG without new code conditions',
+  }),
+  saveButton: byRole('button', { name: 'save' }),
+  statusMessage: byRole('status'),
+  noConditionsNewCodeWarning: byText('project_quality_gate.no_condition_on_new_code'),
+  alertMessage: byRole('alert'),
+};
+
+beforeAll(() => {
+  handler = new QualityGatesServiceMock();
+});
+
+afterEach(() => handler.reset());
+
+it('should require authorization if no permissions set', () => {
+  renderProjectQualityGateApp({}, {});
+  expect(handleRequiredAuthorization).toHaveBeenCalled();
+  expect(ui.qualityGateHeading.query()).not.toBeInTheDocument();
+});
+
+it('should be able to select and save specific Quality Gate', async () => {
+  renderProjectQualityGateApp();
+
+  expect(await ui.qualityGateHeading.find()).toBeInTheDocument();
+  expect(ui.defaultRadioQualityGate.get()).toBeChecked();
+
+  await userEvent.click(ui.specificRadioQualityGate.get());
+  expect(ui.qualityGatesSelect.get()).toBeEnabled();
+
+  await selectEvent.select(ui.qualityGatesSelect.get(), 'Sonar way');
+  await userEvent.click(ui.saveButton.get());
+  expect(ui.statusMessage.get()).toHaveTextContent(/project_quality_gate.success/);
+
+  // Set back default QG
+  await userEvent.click(ui.defaultRadioQualityGate.get());
+  expect(ui.qualityGatesSelect.get()).toBeDisabled();
+  expect(ui.defaultRadioQualityGate.get()).toBeChecked();
+
+  await userEvent.click(ui.saveButton.get());
+  expect(ui.statusMessage.getAll()[1]).toHaveTextContent(/project_quality_gate.success/);
+});
+
+it('shows warning for quality gate that doesnt have conditions on new code', async () => {
+  handler.setGetGateForProjectName('Sonar way');
+  renderProjectQualityGateApp();
+
+  await userEvent.click(await ui.specificRadioQualityGate.find());
+  await selectEvent.select(ui.qualityGatesSelect.get(), 'QG without conditions');
+  expect(ui.QGWithoutConditionsOptionLabel.query()).not.toBeInTheDocument();
+
+  await selectEvent.select(ui.qualityGatesSelect.get(), 'QG without new code conditions');
+  expect(ui.QGWithoutNewCodeConditionOptionLabel.get()).toBeInTheDocument();
+  expect(ui.noConditionsNewCodeWarning.get()).toBeInTheDocument();
+});
+
+it('renders nothing and shows alert when any API fails', async () => {
+  handler.setThrowOnGetGateForProject(true);
+  renderProjectQualityGateApp();
+
+  expect(await ui.alertMessage.find()).toHaveTextContent('unknown');
+  expect(ui.qualityGateHeading.query()).not.toBeInTheDocument();
+});
+
+function renderProjectQualityGateApp(
+  context?: RenderContext,
+  componentOverrides: Partial<Component> = { configuration: { showQualityGates: true } }
+) {
+  renderAppWithComponentContext('project/quality_gate', routes, context, {
+    component: mockComponent(componentOverrides),
+  });
+}
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateApp-test.tsx
deleted file mode 100644 (file)
index 7318d66..0000000
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import {
-  associateGateWithProject,
-  dissociateGateWithProject,
-  fetchQualityGates,
-  getGateForProject,
-  searchProjects,
-} from '../../../api/quality-gates';
-import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization';
-import { mockComponent } from '../../../helpers/mocks/component';
-import { mockQualityGate } from '../../../helpers/mocks/quality-gates';
-import { waitAndUpdate } from '../../../helpers/testUtils';
-import { USE_SYSTEM_DEFAULT } from '../constants';
-import { ProjectQualityGateApp } from '../ProjectQualityGateApp';
-
-jest.mock('../../../api/quality-gates', () => {
-  const { mockQualityGate } = jest.requireActual('../../../helpers/mocks/quality-gates');
-  const { mockCondition } = jest.requireActual('../../../helpers/testMocks');
-
-  const conditions = [mockCondition(), mockCondition({ metric: 'new_bugs' })];
-  const gates = {
-    gate1: mockQualityGate({ id: 'gate1' }),
-    gate2: mockQualityGate({ id: 'gate2', isBuiltIn: true }),
-    gate3: mockQualityGate({ id: 'gate3', isDefault: true }),
-    gate4: mockQualityGate({ id: 'gate4' }),
-  };
-
-  return {
-    associateGateWithProject: jest.fn().mockResolvedValue(null),
-    dissociateGateWithProject: jest.fn().mockResolvedValue(null),
-    fetchQualityGates: jest.fn().mockResolvedValue({
-      qualitygates: Object.values(gates),
-    }),
-    fetchQualityGate: jest.fn().mockImplementation((qg: { id: keyof typeof gates }) => {
-      if (qg.id === 'gate4') {
-        return Promise.reject();
-      }
-      return Promise.resolve({ conditions, ...gates[qg.id] });
-    }),
-    getGateForProject: jest.fn().mockResolvedValue(gates.gate2),
-    searchProjects: jest.fn().mockResolvedValue({ results: [] }),
-  };
-});
-
-jest.mock('../../../helpers/globalMessages', () => ({
-  addGlobalSuccessMessage: jest.fn(),
-}));
-
-jest.mock('../../../app/utils/handleRequiredAuthorization', () => jest.fn());
-
-beforeEach(jest.clearAllMocks);
-
-it('renders correctly', async () => {
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('correctly checks user permissions', () => {
-  shallowRender({ component: mockComponent({ configuration: { showQualityGates: false } }) });
-  expect(handleRequiredAuthorization).toHaveBeenCalled();
-});
-
-it('correctly loads Quality Gate data', async () => {
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-
-  expect(fetchQualityGates).toHaveBeenCalled();
-  expect(getGateForProject).toHaveBeenCalledWith({ project: 'foo' });
-
-  expect(wrapper.state().allQualityGates).toHaveLength(4);
-  expect(wrapper.state().currentQualityGate?.id).toBe('gate2');
-  expect(wrapper.state().selectedQualityGateId).toBe('gate2');
-});
-
-it('correctly fallbacks to the default Quality Gate', async () => {
-  (getGateForProject as jest.Mock).mockResolvedValueOnce(
-    mockQualityGate({ id: 'gate3', isDefault: true })
-  );
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-
-  expect(searchProjects).toHaveBeenCalled();
-
-  expect(wrapper.state().currentQualityGate?.id).toBe('gate3');
-  expect(wrapper.state().selectedQualityGateId).toBe(USE_SYSTEM_DEFAULT);
-});
-
-it('correctly detects if the default Quality Gate was explicitly selected', async () => {
-  (getGateForProject as jest.Mock).mockResolvedValueOnce(
-    mockQualityGate({ id: 'gate3', isDefault: true })
-  );
-  (searchProjects as jest.Mock).mockResolvedValueOnce({
-    results: [{ key: 'foo', selected: true }],
-  });
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-
-  expect(searchProjects).toHaveBeenCalled();
-
-  expect(wrapper.state().currentQualityGate?.id).toBe('gate3');
-  expect(wrapper.state().selectedQualityGateId).toBe('gate3');
-});
-
-it('correctly associates a selected Quality Gate', async () => {
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-
-  wrapper.instance().handleSelect('gate3');
-  wrapper.instance().handleSubmit();
-
-  expect(associateGateWithProject).toHaveBeenCalledWith({
-    gateId: 'gate3',
-    projectKey: 'foo',
-  });
-});
-
-it('correctly associates a project with the system default Quality Gate', async () => {
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-
-  wrapper.setState({
-    currentQualityGate: mockQualityGate({ id: 'gate1' }),
-    selectedQualityGateId: USE_SYSTEM_DEFAULT,
-  });
-  wrapper.instance().handleSubmit();
-
-  expect(dissociateGateWithProject).toHaveBeenCalledWith({
-    projectKey: 'foo',
-  });
-});
-
-it("correctly doesn't do anything if the system default was selected, and the project had no prior Quality Gate associated with it", async () => {
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-
-  wrapper.setState({ currentQualityGate: undefined, selectedQualityGateId: USE_SYSTEM_DEFAULT });
-  wrapper.instance().handleSubmit();
-
-  expect(associateGateWithProject).not.toHaveBeenCalled();
-  expect(dissociateGateWithProject).not.toHaveBeenCalled();
-});
-
-it('correctly handles WS errors', async () => {
-  (fetchQualityGates as jest.Mock).mockRejectedValueOnce(null);
-  (getGateForProject as jest.Mock).mockRejectedValueOnce(null);
-  (searchProjects as jest.Mock).mockRejectedValueOnce(null);
-
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-
-  expect(wrapper.state().allQualityGates).toBeUndefined();
-  expect(wrapper.state().currentQualityGate).toBeUndefined();
-  expect(wrapper.state().loading).toBe(false);
-
-  const result = await wrapper.instance().isUsingDefault(mockQualityGate());
-  expect(result).toBe(false);
-});
-
-function shallowRender(props: Partial<ProjectQualityGateApp['props']> = {}) {
-  return shallow<ProjectQualityGateApp>(
-    <ProjectQualityGateApp
-      component={mockComponent({ key: 'foo', configuration: { showQualityGates: true } })}
-      onComponentChange={jest.fn()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateAppRenderer-test.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/ProjectQualityGateAppRenderer-test.tsx
deleted file mode 100644 (file)
index 47f423f..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import Radio from '../../../components/controls/Radio';
-import Select from '../../../components/controls/Select';
-import { mockQualityGate } from '../../../helpers/mocks/quality-gates';
-import { mockCondition } from '../../../helpers/testMocks';
-import { submit } from '../../../helpers/testUtils';
-import { MetricKey } from '../../../types/metrics';
-import { USE_SYSTEM_DEFAULT } from '../constants';
-import ProjectQualityGateAppRenderer, {
-  ProjectQualityGateAppRendererProps,
-} from '../ProjectQualityGateAppRenderer';
-
-it('should render correctly', () => {
-  expect(shallowRender()).toMatchSnapshot('default');
-  expect(shallowRender({ loading: true })).toMatchSnapshot('loading');
-  expect(shallowRender({ submitting: true })).toMatchSnapshot('submitting');
-  expect(
-    shallowRender({
-      currentQualityGate: mockQualityGate({ id: '2', isDefault: true }),
-      selectedQualityGateId: USE_SYSTEM_DEFAULT,
-    })
-  ).toMatchSnapshot('always use system default');
-  expect(shallowRender({ selectedQualityGateId: '3' })).toMatchSnapshot('show new code warning');
-  expect(
-    shallowRender({
-      selectedQualityGateId: '5',
-    })
-  ).toMatchSnapshot('show warning');
-  expect(
-    shallowRender({
-      selectedQualityGateId: USE_SYSTEM_DEFAULT,
-    })
-  ).toMatchSnapshot('show warning if not using default');
-  expect(shallowRender({ allQualityGates: undefined }).type()).toBeNull(); // no quality gates
-});
-
-it('should render select options correctly', () => {
-  return new Promise<void>((resolve) => {
-    const wrapper = shallowRender();
-    const render = wrapper.find(Select).props().components.Option;
-
-    expect(render).toBeDefined();
-
-    expect(render({ data: { value: '1', label: 'Gate 1' } })).toMatchSnapshot('default');
-    resolve();
-  });
-});
-
-it('should correctly handle changes', () => {
-  const wrapper = shallowRender();
-  const onSelect = jest.fn((selectedQualityGateId) => {
-    wrapper.setProps({ selectedQualityGateId });
-  });
-  wrapper.setProps({ onSelect });
-
-  wrapper.find(Radio).at(0).props().onCheck(USE_SYSTEM_DEFAULT);
-  expect(onSelect).toHaveBeenLastCalledWith(USE_SYSTEM_DEFAULT);
-
-  wrapper.find(Radio).at(1).props().onCheck('1');
-  expect(onSelect).toHaveBeenLastCalledWith('1');
-
-  wrapper.find(Select).props().onChange!({ value: '2' });
-  expect(onSelect).toHaveBeenLastCalledWith('2');
-});
-
-it('should correctly handle form submission', () => {
-  const onSubmit = jest.fn();
-  const wrapper = shallowRender({ onSubmit });
-  submit(wrapper.find('form'));
-  expect(onSubmit).toHaveBeenCalled();
-});
-
-function shallowRender(props: Partial<ProjectQualityGateAppRendererProps> = {}) {
-  const conditions = [mockCondition(), mockCondition({ metric: MetricKey.new_bugs })];
-  const conditionsEmptyOnNew = [mockCondition({ metric: MetricKey.bugs })];
-  return shallow<ProjectQualityGateAppRendererProps>(
-    <ProjectQualityGateAppRenderer
-      allQualityGates={[
-        mockQualityGate({ conditions }),
-        mockQualityGate({ id: '2', isDefault: true, conditions }),
-        mockQualityGate({ id: '3', isDefault: true, conditions: conditionsEmptyOnNew }),
-      ]}
-      currentQualityGate={mockQualityGate({ id: '1' })}
-      loading={false}
-      onSelect={jest.fn()}
-      onSubmit={jest.fn()}
-      selectedQualityGateId="1"
-      submitting={false}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/ProjectQualityGateApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/ProjectQualityGateApp-test.tsx.snap
deleted file mode 100644 (file)
index 49c8689..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders correctly 1`] = `
-<ProjectQualityGateAppRenderer
-  allQualityGates={
-    [
-      {
-        "conditions": [
-          {
-            "error": "10",
-            "id": "1",
-            "metric": "coverage",
-            "op": "LT",
-          },
-          {
-            "error": "10",
-            "id": "1",
-            "metric": "new_bugs",
-            "op": "LT",
-          },
-        ],
-        "id": "gate1",
-        "name": "qualitygate",
-      },
-      {
-        "conditions": [
-          {
-            "error": "10",
-            "id": "1",
-            "metric": "coverage",
-            "op": "LT",
-          },
-          {
-            "error": "10",
-            "id": "1",
-            "metric": "new_bugs",
-            "op": "LT",
-          },
-        ],
-        "id": "gate2",
-        "isBuiltIn": true,
-        "name": "qualitygate",
-      },
-      {
-        "conditions": [
-          {
-            "error": "10",
-            "id": "1",
-            "metric": "coverage",
-            "op": "LT",
-          },
-          {
-            "error": "10",
-            "id": "1",
-            "metric": "new_bugs",
-            "op": "LT",
-          },
-        ],
-        "id": "gate3",
-        "isDefault": true,
-        "name": "qualitygate",
-      },
-      {
-        "conditions": [
-          {
-            "error": "10",
-            "id": "1",
-            "metric": "coverage",
-            "op": "LT",
-          },
-          {
-            "error": "10",
-            "id": "1",
-            "metric": "new_bugs",
-            "op": "LT",
-          },
-        ],
-        "id": "gate4",
-        "name": "qualitygate",
-      },
-    ]
-  }
-  currentQualityGate={
-    {
-      "id": "gate2",
-      "isBuiltIn": true,
-      "name": "qualitygate",
-    }
-  }
-  loading={false}
-  onSelect={[Function]}
-  onSubmit={[Function]}
-  selectedQualityGateId="gate2"
-  submitting={false}
-/>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/ProjectQualityGateAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/ProjectQualityGateAppRenderer-test.tsx.snap
deleted file mode 100644 (file)
index a2da5f6..0000000
+++ /dev/null
@@ -1,1020 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: always use system default 1`] = `
-<div
-  className="page page-limited"
-  id="project-quality-gate"
->
-  <Suggestions
-    suggestions="project_quality_gate"
-  />
-  <Helmet
-    defer={false}
-    encodeSpecialCharacters={true}
-    prioritizeSeoTags={false}
-    title="project_quality_gate.page"
-  />
-  <A11ySkipTarget
-    anchor="qg_main"
-  />
-  <header
-    className="page-header"
-  >
-    <div
-      className="page-title display-flex-center"
-    >
-      <h1>
-        project_quality_gate.page
-      </h1>
-      <HelpTooltip
-        className="spacer-left"
-        overlay={
-          <div
-            className="big-padded-top big-padded-bottom"
-          >
-            quality_gates.projects.help
-          </div>
-        }
-      />
-    </div>
-  </header>
-  <div
-    className="boxed-group"
-  >
-    <h2
-      className="boxed-group-header"
-    >
-      project_quality_gate.subtitle
-    </h2>
-    <form
-      className="boxed-group-inner"
-      onSubmit={[Function]}
-    >
-      <p
-        className="big-spacer-bottom"
-      >
-        project_quality_gate.page.description
-      </p>
-      <div
-        className="big-spacer-bottom"
-      >
-        <Radio
-          checked={true}
-          className="display-flex-start"
-          disabled={false}
-          onCheck={[Function]}
-          value="-1"
-        >
-          <div
-            className="spacer-left"
-          >
-            <div
-              className="little-spacer-bottom"
-            >
-              project_quality_gate.always_use_default
-            </div>
-            <div
-              className="display-flex-center"
-            >
-              <span
-                className="text-muted little-spacer-right"
-              >
-                current_noun
-                :
-              </span>
-              qualitygate
-            </div>
-          </div>
-        </Radio>
-      </div>
-      <div
-        className="big-spacer-bottom"
-      >
-        <Radio
-          checked={false}
-          className="display-flex-start"
-          disabled={false}
-          onCheck={[Function]}
-          value="2"
-        >
-          <div
-            className="spacer-left"
-          >
-            <div
-              className="little-spacer-bottom"
-            >
-              project_quality_gate.always_use_specific
-            </div>
-            <div
-              className="display-flex-center"
-            >
-              <Select
-                className="abs-width-300 it__project-quality-gate-select"
-                components={
-                  {
-                    "Option": [Function],
-                  }
-                }
-                isClearable={true}
-                isDisabled={true}
-                onChange={[Function]}
-                options={
-                  [
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "1",
-                    },
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "2",
-                    },
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "3",
-                    },
-                  ]
-                }
-              />
-            </div>
-          </div>
-        </Radio>
-      </div>
-      <div>
-        <SubmitButton
-          disabled={false}
-        >
-          save
-        </SubmitButton>
-      </div>
-    </form>
-  </div>
-</div>
-`;
-
-exports[`should render correctly: default 1`] = `
-<div
-  className="page page-limited"
-  id="project-quality-gate"
->
-  <Suggestions
-    suggestions="project_quality_gate"
-  />
-  <Helmet
-    defer={false}
-    encodeSpecialCharacters={true}
-    prioritizeSeoTags={false}
-    title="project_quality_gate.page"
-  />
-  <A11ySkipTarget
-    anchor="qg_main"
-  />
-  <header
-    className="page-header"
-  >
-    <div
-      className="page-title display-flex-center"
-    >
-      <h1>
-        project_quality_gate.page
-      </h1>
-      <HelpTooltip
-        className="spacer-left"
-        overlay={
-          <div
-            className="big-padded-top big-padded-bottom"
-          >
-            quality_gates.projects.help
-          </div>
-        }
-      />
-    </div>
-  </header>
-  <div
-    className="boxed-group"
-  >
-    <h2
-      className="boxed-group-header"
-    >
-      project_quality_gate.subtitle
-    </h2>
-    <form
-      className="boxed-group-inner"
-      onSubmit={[Function]}
-    >
-      <p
-        className="big-spacer-bottom"
-      >
-        project_quality_gate.page.description
-      </p>
-      <div
-        className="big-spacer-bottom"
-      >
-        <Radio
-          checked={false}
-          className="display-flex-start"
-          disabled={false}
-          onCheck={[Function]}
-          value="-1"
-        >
-          <div
-            className="spacer-left"
-          >
-            <div
-              className="little-spacer-bottom"
-            >
-              project_quality_gate.always_use_default
-            </div>
-            <div
-              className="display-flex-center"
-            >
-              <span
-                className="text-muted little-spacer-right"
-              >
-                current_noun
-                :
-              </span>
-              qualitygate
-            </div>
-          </div>
-        </Radio>
-      </div>
-      <div
-        className="big-spacer-bottom"
-      >
-        <Radio
-          checked={true}
-          className="display-flex-start"
-          disabled={false}
-          onCheck={[Function]}
-          value="1"
-        >
-          <div
-            className="spacer-left"
-          >
-            <div
-              className="little-spacer-bottom"
-            >
-              project_quality_gate.always_use_specific
-            </div>
-            <div
-              className="display-flex-center"
-            >
-              <Select
-                className="abs-width-300 it__project-quality-gate-select"
-                components={
-                  {
-                    "Option": [Function],
-                  }
-                }
-                isClearable={false}
-                isDisabled={false}
-                onChange={[Function]}
-                options={
-                  [
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "1",
-                    },
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "2",
-                    },
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "3",
-                    },
-                  ]
-                }
-                value={
-                  {
-                    "isDisabled": false,
-                    "label": "qualitygate",
-                    "value": "1",
-                  }
-                }
-              />
-            </div>
-          </div>
-        </Radio>
-      </div>
-      <div>
-        <SubmitButton
-          disabled={false}
-        >
-          save
-        </SubmitButton>
-      </div>
-    </form>
-  </div>
-</div>
-`;
-
-exports[`should render correctly: loading 1`] = `
-<i
-  className="spinner"
-/>
-`;
-
-exports[`should render correctly: show new code warning 1`] = `
-<div
-  className="page page-limited"
-  id="project-quality-gate"
->
-  <Suggestions
-    suggestions="project_quality_gate"
-  />
-  <Helmet
-    defer={false}
-    encodeSpecialCharacters={true}
-    prioritizeSeoTags={false}
-    title="project_quality_gate.page"
-  />
-  <A11ySkipTarget
-    anchor="qg_main"
-  />
-  <header
-    className="page-header"
-  >
-    <div
-      className="page-title display-flex-center"
-    >
-      <h1>
-        project_quality_gate.page
-      </h1>
-      <HelpTooltip
-        className="spacer-left"
-        overlay={
-          <div
-            className="big-padded-top big-padded-bottom"
-          >
-            quality_gates.projects.help
-          </div>
-        }
-      />
-    </div>
-  </header>
-  <div
-    className="boxed-group"
-  >
-    <h2
-      className="boxed-group-header"
-    >
-      project_quality_gate.subtitle
-    </h2>
-    <form
-      className="boxed-group-inner"
-      onSubmit={[Function]}
-    >
-      <p
-        className="big-spacer-bottom"
-      >
-        project_quality_gate.page.description
-      </p>
-      <div
-        className="big-spacer-bottom"
-      >
-        <Radio
-          checked={false}
-          className="display-flex-start"
-          disabled={false}
-          onCheck={[Function]}
-          value="-1"
-        >
-          <div
-            className="spacer-left"
-          >
-            <div
-              className="little-spacer-bottom"
-            >
-              project_quality_gate.always_use_default
-            </div>
-            <div
-              className="display-flex-center"
-            >
-              <span
-                className="text-muted little-spacer-right"
-              >
-                current_noun
-                :
-              </span>
-              qualitygate
-            </div>
-          </div>
-        </Radio>
-      </div>
-      <div
-        className="big-spacer-bottom"
-      >
-        <Radio
-          checked={true}
-          className="display-flex-start"
-          disabled={false}
-          onCheck={[Function]}
-          value="3"
-        >
-          <div
-            className="spacer-left"
-          >
-            <div
-              className="little-spacer-bottom"
-            >
-              project_quality_gate.always_use_specific
-            </div>
-            <div
-              className="display-flex-center"
-            >
-              <Select
-                className="abs-width-300 it__project-quality-gate-select"
-                components={
-                  {
-                    "Option": [Function],
-                  }
-                }
-                isClearable={false}
-                isDisabled={false}
-                onChange={[Function]}
-                options={
-                  [
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "1",
-                    },
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "2",
-                    },
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "3",
-                    },
-                  ]
-                }
-                value={
-                  {
-                    "isDisabled": false,
-                    "label": "qualitygate",
-                    "value": "3",
-                  }
-                }
-              />
-            </div>
-          </div>
-        </Radio>
-        <Alert
-          className="abs-width-600 spacer-top"
-          variant="warning"
-        >
-          <FormattedMessage
-            defaultMessage="project_quality_gate.no_condition_on_new_code"
-            id="project_quality_gate.no_condition_on_new_code"
-            values={
-              {
-                "link": <ForwardRef(Link)
-                  to={
-                    {
-                      "pathname": "/quality_gates/show/qualitygate",
-                    }
-                  }
-                >
-                  project_quality_gate.no_condition.link
-                </ForwardRef(Link)>,
-              }
-            }
-          />
-        </Alert>
-        <Alert
-          className="big-spacer-top abs-width-600"
-          variant="warning"
-        >
-          project_quality_gate.requires_new_analysis
-        </Alert>
-      </div>
-      <div>
-        <SubmitButton
-          disabled={false}
-        >
-          save
-        </SubmitButton>
-      </div>
-    </form>
-  </div>
-</div>
-`;
-
-exports[`should render correctly: show warning 1`] = `
-<div
-  className="page page-limited"
-  id="project-quality-gate"
->
-  <Suggestions
-    suggestions="project_quality_gate"
-  />
-  <Helmet
-    defer={false}
-    encodeSpecialCharacters={true}
-    prioritizeSeoTags={false}
-    title="project_quality_gate.page"
-  />
-  <A11ySkipTarget
-    anchor="qg_main"
-  />
-  <header
-    className="page-header"
-  >
-    <div
-      className="page-title display-flex-center"
-    >
-      <h1>
-        project_quality_gate.page
-      </h1>
-      <HelpTooltip
-        className="spacer-left"
-        overlay={
-          <div
-            className="big-padded-top big-padded-bottom"
-          >
-            quality_gates.projects.help
-          </div>
-        }
-      />
-    </div>
-  </header>
-  <div
-    className="boxed-group"
-  >
-    <h2
-      className="boxed-group-header"
-    >
-      project_quality_gate.subtitle
-    </h2>
-    <form
-      className="boxed-group-inner"
-      onSubmit={[Function]}
-    >
-      <p
-        className="big-spacer-bottom"
-      >
-        project_quality_gate.page.description
-      </p>
-      <div
-        className="big-spacer-bottom"
-      >
-        <Radio
-          checked={false}
-          className="display-flex-start"
-          disabled={false}
-          onCheck={[Function]}
-          value="-1"
-        >
-          <div
-            className="spacer-left"
-          >
-            <div
-              className="little-spacer-bottom"
-            >
-              project_quality_gate.always_use_default
-            </div>
-            <div
-              className="display-flex-center"
-            >
-              <span
-                className="text-muted little-spacer-right"
-              >
-                current_noun
-                :
-              </span>
-              qualitygate
-            </div>
-          </div>
-        </Radio>
-      </div>
-      <div
-        className="big-spacer-bottom"
-      >
-        <Radio
-          checked={true}
-          className="display-flex-start"
-          disabled={false}
-          onCheck={[Function]}
-          value="5"
-        >
-          <div
-            className="spacer-left"
-          >
-            <div
-              className="little-spacer-bottom"
-            >
-              project_quality_gate.always_use_specific
-            </div>
-            <div
-              className="display-flex-center"
-            >
-              <Select
-                className="abs-width-300 it__project-quality-gate-select"
-                components={
-                  {
-                    "Option": [Function],
-                  }
-                }
-                isClearable={false}
-                isDisabled={false}
-                onChange={[Function]}
-                options={
-                  [
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "1",
-                    },
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "2",
-                    },
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "3",
-                    },
-                  ]
-                }
-              />
-            </div>
-          </div>
-        </Radio>
-        <Alert
-          className="big-spacer-top abs-width-600"
-          variant="warning"
-        >
-          project_quality_gate.requires_new_analysis
-        </Alert>
-      </div>
-      <div>
-        <SubmitButton
-          disabled={false}
-        >
-          save
-        </SubmitButton>
-      </div>
-    </form>
-  </div>
-</div>
-`;
-
-exports[`should render correctly: show warning if not using default 1`] = `
-<div
-  className="page page-limited"
-  id="project-quality-gate"
->
-  <Suggestions
-    suggestions="project_quality_gate"
-  />
-  <Helmet
-    defer={false}
-    encodeSpecialCharacters={true}
-    prioritizeSeoTags={false}
-    title="project_quality_gate.page"
-  />
-  <A11ySkipTarget
-    anchor="qg_main"
-  />
-  <header
-    className="page-header"
-  >
-    <div
-      className="page-title display-flex-center"
-    >
-      <h1>
-        project_quality_gate.page
-      </h1>
-      <HelpTooltip
-        className="spacer-left"
-        overlay={
-          <div
-            className="big-padded-top big-padded-bottom"
-          >
-            quality_gates.projects.help
-          </div>
-        }
-      />
-    </div>
-  </header>
-  <div
-    className="boxed-group"
-  >
-    <h2
-      className="boxed-group-header"
-    >
-      project_quality_gate.subtitle
-    </h2>
-    <form
-      className="boxed-group-inner"
-      onSubmit={[Function]}
-    >
-      <p
-        className="big-spacer-bottom"
-      >
-        project_quality_gate.page.description
-      </p>
-      <div
-        className="big-spacer-bottom"
-      >
-        <Radio
-          checked={true}
-          className="display-flex-start"
-          disabled={false}
-          onCheck={[Function]}
-          value="-1"
-        >
-          <div
-            className="spacer-left"
-          >
-            <div
-              className="little-spacer-bottom"
-            >
-              project_quality_gate.always_use_default
-            </div>
-            <div
-              className="display-flex-center"
-            >
-              <span
-                className="text-muted little-spacer-right"
-              >
-                current_noun
-                :
-              </span>
-              qualitygate
-            </div>
-          </div>
-        </Radio>
-      </div>
-      <div
-        className="big-spacer-bottom"
-      >
-        <Radio
-          checked={false}
-          className="display-flex-start"
-          disabled={false}
-          onCheck={[Function]}
-          value="1"
-        >
-          <div
-            className="spacer-left"
-          >
-            <div
-              className="little-spacer-bottom"
-            >
-              project_quality_gate.always_use_specific
-            </div>
-            <div
-              className="display-flex-center"
-            >
-              <Select
-                className="abs-width-300 it__project-quality-gate-select"
-                components={
-                  {
-                    "Option": [Function],
-                  }
-                }
-                isClearable={true}
-                isDisabled={true}
-                onChange={[Function]}
-                options={
-                  [
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "1",
-                    },
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "2",
-                    },
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "3",
-                    },
-                  ]
-                }
-              />
-            </div>
-          </div>
-        </Radio>
-        <Alert
-          className="big-spacer-top abs-width-600"
-          variant="warning"
-        >
-          project_quality_gate.requires_new_analysis
-        </Alert>
-      </div>
-      <div>
-        <SubmitButton
-          disabled={false}
-        >
-          save
-        </SubmitButton>
-      </div>
-    </form>
-  </div>
-</div>
-`;
-
-exports[`should render correctly: submitting 1`] = `
-<div
-  className="page page-limited"
-  id="project-quality-gate"
->
-  <Suggestions
-    suggestions="project_quality_gate"
-  />
-  <Helmet
-    defer={false}
-    encodeSpecialCharacters={true}
-    prioritizeSeoTags={false}
-    title="project_quality_gate.page"
-  />
-  <A11ySkipTarget
-    anchor="qg_main"
-  />
-  <header
-    className="page-header"
-  >
-    <div
-      className="page-title display-flex-center"
-    >
-      <h1>
-        project_quality_gate.page
-      </h1>
-      <HelpTooltip
-        className="spacer-left"
-        overlay={
-          <div
-            className="big-padded-top big-padded-bottom"
-          >
-            quality_gates.projects.help
-          </div>
-        }
-      />
-    </div>
-  </header>
-  <div
-    className="boxed-group"
-  >
-    <h2
-      className="boxed-group-header"
-    >
-      project_quality_gate.subtitle
-    </h2>
-    <form
-      className="boxed-group-inner"
-      onSubmit={[Function]}
-    >
-      <p
-        className="big-spacer-bottom"
-      >
-        project_quality_gate.page.description
-      </p>
-      <div
-        className="big-spacer-bottom"
-      >
-        <Radio
-          checked={false}
-          className="display-flex-start"
-          disabled={true}
-          onCheck={[Function]}
-          value="-1"
-        >
-          <div
-            className="spacer-left"
-          >
-            <div
-              className="little-spacer-bottom"
-            >
-              project_quality_gate.always_use_default
-            </div>
-            <div
-              className="display-flex-center"
-            >
-              <span
-                className="text-muted little-spacer-right"
-              >
-                current_noun
-                :
-              </span>
-              qualitygate
-            </div>
-          </div>
-        </Radio>
-      </div>
-      <div
-        className="big-spacer-bottom"
-      >
-        <Radio
-          checked={true}
-          className="display-flex-start"
-          disabled={true}
-          onCheck={[Function]}
-          value="1"
-        >
-          <div
-            className="spacer-left"
-          >
-            <div
-              className="little-spacer-bottom"
-            >
-              project_quality_gate.always_use_specific
-            </div>
-            <div
-              className="display-flex-center"
-            >
-              <Select
-                className="abs-width-300 it__project-quality-gate-select"
-                components={
-                  {
-                    "Option": [Function],
-                  }
-                }
-                isClearable={false}
-                isDisabled={true}
-                onChange={[Function]}
-                options={
-                  [
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "1",
-                    },
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "2",
-                    },
-                    {
-                      "isDisabled": false,
-                      "label": "qualitygate",
-                      "value": "3",
-                    },
-                  ]
-                }
-                value={
-                  {
-                    "isDisabled": false,
-                    "label": "qualitygate",
-                    "value": "1",
-                  }
-                }
-              />
-            </div>
-          </div>
-        </Radio>
-      </div>
-      <div>
-        <SubmitButton
-          disabled={true}
-        >
-          save
-        </SubmitButton>
-        <i
-          className="spinner spacer-left"
-        />
-      </div>
-    </form>
-  </div>
-</div>
-`;
-
-exports[`should render select options correctly: default 1`] = `
-<Option
-  data={
-    {
-      "label": "Gate 1",
-      "value": "1",
-    }
-  }
->
-  <div>
-    <DisableableSelectOption
-      className="abs-width-100"
-      disableTooltipOverlay={[Function]}
-      disabledReason="project_quality_gate.no_condition.reason"
-      option={
-        {
-          "label": "Gate 1",
-          "value": "1",
-        }
-      }
-    />
-  </div>
-</Option>
-`;
index 899a9ba858eb70eb4ef884f44030400cff92db55..623d21ad35bcc00ee7d816853d4bf39cbc2f15c4 100644 (file)
@@ -101,8 +101,8 @@ class App extends React.PureComponent<Props, State> {
     this.setState(({ qualityGates }) => {
       return {
         qualityGates: qualityGates.map((candidate) => {
-          if (candidate.isDefault || candidate.id === qualityGate.id) {
-            return { ...candidate, isDefault: candidate.id === qualityGate.id };
+          if (candidate.isDefault || candidate.name === qualityGate.name) {
+            return { ...candidate, isDefault: candidate.name === qualityGate.name };
           }
           return candidate;
         }),
index b542d00ea24e61c97495eb0bda7a28f2381f4bc7..70a12743911b7f71a0ba265142ba29d1fed67b89 100644 (file)
@@ -73,7 +73,7 @@ export default class ConditionModal extends React.PureComponent<Props, State> {
     };
     const submitPromise = condition
       ? updateCondition({ id: condition.id, ...newCondition })
-      : createCondition({ gateId: qualityGate.id, ...newCondition });
+      : createCondition({ gateName: qualityGate.name, ...newCondition });
     return submitPromise.then(this.props.onAddCondition);
   };
 
index 328ad0dffc29187b5a7fded06c2aa2479418be17..6497b1a01d10e124166ec58bb9fac7312dd35699 100644 (file)
@@ -73,7 +73,7 @@ export default class CaycReviewUpdateConditionsModal extends React.PureComponent
       promiseArr.push(
         createCondition({
           ...getCorrectCaycCondition(condition),
-          gateId: qualityGate.id,
+          gateName: qualityGate.name,
         })
           .then((resultCondition) => this.props.onAddCondition(resultCondition))
           .catch(() => undefined)
index 3d9b55f52e5d6aebbe454ddd820ccee13419e276..688ceab7bcecce6f49fad516d379b8ff782dace3 100644 (file)
@@ -75,7 +75,7 @@ export function DetailsContent(props: DetailsContentProps) {
             <Projects
               canEdit={actions.associateProjects}
               // pass unique key to re-mount the component when the quality gate changes
-              key={qualityGate.id}
+              key={qualityGate.name}
               qualityGate={qualityGate}
             />
           )}
index b1f4ffe15bd9860ffd0836a4c158bdb1d85cca47..c20cb92ae61a592a6f369b30c5fa0def26ac63b5 100644 (file)
@@ -38,8 +38,7 @@ export default function List({ qualityGates, currentQualityGate }: Props) {
         <NavLink
           className="list-group-item display-flex-center"
           aria-current={currentQualityGate === qualityGate.name && 'page'}
-          data-id={qualityGate.id}
-          key={qualityGate.id}
+          key={qualityGate.name}
           to={getQualityGateUrl(qualityGate.name)}
         >
           <span className="flex-1 text-ellipsis" title={qualityGate.name}>
index 8eef08b940a6ef4ab3421ead2ef548840635f247..eb267eb263dad81865b0f6498510ac5f37cb5c99 100644 (file)
@@ -105,7 +105,7 @@ export default class Projects extends React.PureComponent<Props, State> {
 
   handleSelect = (key: string) =>
     associateGateWithProject({
-      gateId: this.props.qualityGate.id,
+      gateName: this.props.qualityGate.name,
       projectKey: key,
     }).then(() => {
       if (this.mounted) {
index 0235b6a0a6cc6ac48676894b7b99c0f6f0a3358f..c617b35f7a62411e7998e9bb1e74fa6b0504188c 100644 (file)
@@ -62,7 +62,7 @@ export default class QualityGatePermissions extends React.Component<Props, State
   }
 
   componentDidUpdate(newProps: Props) {
-    if (this.props.qualityGate.id !== newProps.qualityGate.id) {
+    if (this.props.qualityGate.name !== newProps.qualityGate.name) {
       this.fetchPermissions();
     }
   }
index be68153f64b82951d3041baa62ff51ea373f6c6e..ea3598d899fe3097d2155019cc684726239570b4 100644 (file)
@@ -114,8 +114,7 @@ export function getCorrectCaycCondition(condition: Condition) {
 }
 
 export function checkIfDefault(qualityGate: QualityGate, list: QualityGate[]): boolean {
-  const finding = list.find((candidate) => candidate.id === qualityGate.id);
-  return (finding && finding.isDefault) || false;
+  return list.find((candidate) => candidate.name === qualityGate.name)?.isDefault ?? false;
 }
 
 export function addCondition(qualityGate: QualityGate, condition: Condition): QualityGate {
index ce49bd097c28ec719c974f5d295f0f7a231710d4..0579b5ddd52ead17d18a850e537bcacfb2d837a4 100644 (file)
@@ -29,7 +29,6 @@ import { mockMeasureEnhanced, mockMetric } from '../testMocks';
 
 export function mockQualityGate(overrides: Partial<QualityGate> = {}): QualityGate {
   return {
-    id: '1',
     name: 'qualitygate',
     ...overrides,
   };
index a504213da939480eb83cf74e681842dcf4613172..9344fcf32e4090b6bacc59f61aee285a9ca7a421 100644 (file)
@@ -517,7 +517,6 @@ export interface QualityGate {
     setAsDefault?: boolean;
   };
   conditions?: Condition[];
-  id: string;
   isBuiltIn?: boolean;
   isCaycCompliant?: boolean;
   isDefault?: boolean;
index 469684541681a1f76df782ae22ba2dd43f4bcd0e..4c8404dea1debf34169254c345046fbe1b466689 100644 (file)
@@ -1611,6 +1611,7 @@ project_quality_gate.successfully_updated=Quality Gate has been successfully upd
 project_quality_gate.subtitle=Manage project Quality Gate
 project_quality_gate.always_use_default=Always use the instance default Quality Gate
 project_quality_gate.always_use_specific=Always use a specific Quality Gate
+project_quality_gate.select_specific_qg=Select Quality Gate
 project_quality_gate.requires_new_analysis=Changes will be applied after the next analysis.
 project_quality_gate.no_condition=This Quality Gate is empty. To make it usable, add conditions to the {link}.
 project_quality_gate.no_condition_on_new_code=This Quality Gate sets conditions on overall code but not on new code. It will not appear on pull requests. To enable it for pull requests, add conditions to the {link}.