]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18217 Correct order of conditions in quality gate page
authorstanislavh <stanislav.honcharov@sonarsource.com>
Fri, 17 Feb 2023 13:32:39 +0000 (14:32 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 20 Feb 2023 20:03:01 +0000 (20:03 +0000)
server/sonar-web/src/main/js/apps/quality-gates/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
server/sonar-web/src/main/js/apps/quality-gates/utils.ts

index 3fd313aadcdb607c61f9dc3e3c6f7964d0df17d9..93c76b35343d79946e676d718125ad93c61b8f2e 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { mockMetric } from '../../../helpers/testMocks';
-import { getLocalizedMetricNameNoDiffMetric } from '../utils';
+import { mockCondition, mockMetric } from '../../../helpers/testMocks';
+import { MetricKey } from '../../../types/metrics';
+import { Condition } from '../../../types/types';
+import { getLocalizedMetricNameNoDiffMetric, groupAndSortByPriorityConditions } from '../utils';
 
 const METRICS = {
-  bugs: mockMetric({ key: 'bugs', name: 'Bugs' }),
   existing_metric: mockMetric(),
-  new_maintainability_rating: mockMetric(),
-  sqale_rating: mockMetric({ key: 'sqale_rating', name: 'Maintainability Rating' }),
+  [MetricKey.new_maintainability_rating]: mockMetric({ name: 'New Maintainability Rating' }),
+  [MetricKey.sqale_rating]: mockMetric({
+    name: 'Maintainability Rating',
+  }),
+  [MetricKey.coverage]: mockMetric({ name: 'Coverage' }),
+  [MetricKey.bugs]: mockMetric({ name: 'Bugs' }),
+  [MetricKey.new_coverage]: mockMetric({ name: 'New Code Coverage' }),
+  [MetricKey.new_reliability_rating]: mockMetric({ name: 'New Reliability Rating' }),
+  [MetricKey.new_bugs]: mockMetric({ name: 'New Bugs' }),
+  [MetricKey.code_smells]: mockMetric({ name: 'Code Smells' }),
+  [MetricKey.duplicated_lines_density]: mockMetric({ name: 'Duplicated lines (%)' }),
 };
 
 describe('getLocalizedMetricNameNoDiffMetric', () => {
@@ -44,3 +54,36 @@ describe('getLocalizedMetricNameNoDiffMetric', () => {
     ).toBe('Maintainability Rating');
   });
 });
+
+describe('groupAndSortByPriorityConditions', () => {
+  const conditions = [
+    mockCondition(),
+    mockCondition({ metric: MetricKey.bugs }),
+    mockCondition({ metric: MetricKey.new_coverage }),
+    mockCondition({ metric: MetricKey.new_reliability_rating }),
+    mockCondition({ metric: MetricKey.code_smells }),
+    mockCondition({ metric: MetricKey.duplicated_lines_density }),
+    mockCondition({ metric: MetricKey.new_bugs }),
+  ];
+  const expectedConditionsOrderNewCode = [
+    MetricKey.new_reliability_rating,
+    MetricKey.new_coverage,
+    MetricKey.new_bugs,
+  ];
+  const expectConditionsOrderOverallCode = [
+    MetricKey.bugs,
+    MetricKey.code_smells,
+    MetricKey.coverage,
+    MetricKey.duplicated_lines_density,
+  ];
+
+  it('should return grouped conditions by overall/new code and sort them by CAYC order', () => {
+    const result = groupAndSortByPriorityConditions(conditions, METRICS);
+    const conditionsMap = ({ metric }: Condition) => metric;
+
+    expect(result.newCodeConditions.map(conditionsMap)).toEqual(expectedConditionsOrderNewCode);
+    expect(result.overallCodeConditions.map(conditionsMap)).toEqual(
+      expectConditionsOrderOverallCode
+    );
+  });
+});
index 7da4afdb0fbab3db6db8608596383dd97af4e2a9..10885d4f92393d0455f1d761535845c827c969ed 100644 (file)
@@ -17,7 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { differenceWith, map, sortBy, uniqBy } from 'lodash';
+import { differenceWith, map, uniqBy } from 'lodash';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import withAvailableFeatures, {
@@ -30,7 +30,6 @@ import { Button } from '../../../components/controls/buttons';
 import ModalButton, { ModalProps } from '../../../components/controls/ModalButton';
 import { Alert } from '../../../components/ui/Alert';
 import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
-import { isDiffMetric } from '../../../helpers/measures';
 import { Feature } from '../../../types/features';
 import { MetricKey } from '../../../types/metrics';
 import {
@@ -40,6 +39,7 @@ import {
   Metric,
   QualityGate,
 } from '../../../types/types';
+import { groupAndSortByPriorityConditions } from '../utils';
 import ConditionModal from './ConditionModal';
 import CaycReviewUpdateConditionsModal from './ConditionReviewAndUpdateModal';
 import ConditionsTable from './ConditionsTable';
@@ -140,16 +140,9 @@ export class Conditions extends React.PureComponent<Props, State> {
     const { unlockEditing } = this.state;
     const { conditions = [] } = qualityGate;
     const existingConditions = conditions.filter((condition) => metrics[condition.metric]);
-    const sortedConditions = sortBy(
+    const { overallCodeConditions, newCodeConditions } = groupAndSortByPriorityConditions(
       existingConditions,
-      (condition) => metrics[condition.metric] && metrics[condition.metric].name
-    );
-
-    const sortedConditionsOnOverallMetrics = sortedConditions.filter(
-      (condition) => !isDiffMetric(condition.metric)
-    );
-    const sortedConditionsOnNewMetrics = sortedConditions.filter((condition) =>
-      isDiffMetric(condition.metric)
+      metrics
     );
 
     const duplicates: ConditionType[] = [];
@@ -289,7 +282,7 @@ export class Conditions extends React.PureComponent<Props, State> {
           </Alert>
         )}
 
-        {sortedConditionsOnNewMetrics.length > 0 && (
+        {newCodeConditions.length > 0 && (
           <div className="big-spacer-top">
             <h3 className="medium text-normal">
               {translate('quality_gates.conditions.new_code', 'long')}
@@ -306,14 +299,14 @@ export class Conditions extends React.PureComponent<Props, State> {
               onRemoveCondition={onRemoveCondition}
               onSaveCondition={onSaveCondition}
               updatedConditionId={updatedConditionId}
-              conditions={sortedConditionsOnNewMetrics}
+              conditions={newCodeConditions}
               showEdit={this.state.unlockEditing}
               scope="new"
             />
           </div>
         )}
 
-        {sortedConditionsOnOverallMetrics.length > 0 && (
+        {overallCodeConditions.length > 0 && (
           <div className="big-spacer-top">
             <h3 className="medium text-normal">
               {translate('quality_gates.conditions.overall_code', 'long')}
@@ -332,7 +325,7 @@ export class Conditions extends React.PureComponent<Props, State> {
               onRemoveCondition={onRemoveCondition}
               onSaveCondition={onSaveCondition}
               updatedConditionId={updatedConditionId}
-              conditions={sortedConditionsOnOverallMetrics}
+              conditions={overallCodeConditions}
               scope="overall"
             />
           </div>
index 80b99e872204991bcee3e269dd50b6cbe1891077..502f32cb511938a792683d343333f379a079087c 100644 (file)
  */
 import { getLocalizedMetricName } from '../../helpers/l10n';
 import { isDiffMetric } from '../../helpers/measures';
+import { MetricKey } from '../../types/metrics';
 import { CaycStatus, Condition, Dict, Metric, QualityGate } from '../../types/types';
 
+interface GroupedByMetricConditions {
+  overallCodeConditions: Condition[];
+  newCodeConditions: Condition[];
+}
+
 const CAYC_CONDITIONS: { [key: string]: Condition } = {
   new_reliability_rating: {
     error: '1',
@@ -60,6 +66,17 @@ const CAYC_CONDITIONS: { [key: string]: Condition } = {
   },
 };
 
+const CAYC_CONDITION_ORDER_PRIORITIES: Dict<number> = [
+  MetricKey.new_reliability_rating,
+  MetricKey.new_security_rating,
+  MetricKey.new_security_hotspots_reviewed,
+  MetricKey.new_maintainability_rating,
+  MetricKey.new_coverage,
+  MetricKey.new_duplicated_lines_density,
+]
+  .reverse()
+  .reduce((acc, key, i) => ({ ...acc, [key.toString()]: i + 1 }), {} as Dict<number>);
+
 export const CAYC_CONDITIONS_WITHOUT_FIXED_VALUE = ['new_duplicated_lines_density', 'new_coverage'];
 export const CAYC_CONDITIONS_WITH_FIXED_VALUE = [
   'new_security_hotspots_reviewed',
@@ -104,6 +121,45 @@ export function getCaycConditionsWithCorrectValue(conditions: Condition[]) {
   });
 }
 
+export function groupConditionsByMetric(conditions: Condition[]): GroupedByMetricConditions {
+  return conditions.reduce(
+    (result, condition) => {
+      const isNewCode = isDiffMetric(condition.metric);
+      result[isNewCode ? 'newCodeConditions' : 'overallCodeConditions'].push(condition);
+
+      return result;
+    },
+    {
+      overallCodeConditions: [] as Condition[],
+      newCodeConditions: [] as Condition[],
+    }
+  );
+}
+
+export function groupAndSortByPriorityConditions(
+  conditions: Condition[],
+  metrics: Dict<Metric>
+): GroupedByMetricConditions {
+  const groupedConditions = groupConditionsByMetric(conditions);
+
+  function sortFn(a: Condition, b: Condition) {
+    const priorityA = CAYC_CONDITION_ORDER_PRIORITIES[a.metric] ?? 0;
+    const priorityB = CAYC_CONDITION_ORDER_PRIORITIES[b.metric] ?? 0;
+    const diff = priorityB - priorityA;
+    if (diff !== 0) {
+      return diff;
+    }
+    return metrics[a.metric].name.localeCompare(metrics[b.metric].name, undefined, {
+      sensitivity: 'base',
+    });
+  }
+
+  groupedConditions.newCodeConditions.sort(sortFn);
+  groupedConditions.overallCodeConditions.sort(sortFn);
+
+  return groupedConditions;
+}
+
 export function getCorrectCaycCondition(condition: Condition) {
   if (CAYC_CONDITIONS_WITHOUT_FIXED_VALUE.includes(condition.metric)) {
     return condition;