]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12965 Configure Security Review domain in measures page
authorJeremy Davis <jeremy.davis@sonarsource.com>
Wed, 29 Jan 2020 14:18:49 +0000 (15:18 +0100)
committerSonarTech <sonartech@sonarsource.com>
Tue, 11 Feb 2020 19:46:11 +0000 (20:46 +0100)
server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/component-measures/components/App.tsx
server/sonar-web/src/main/js/apps/component-measures/config/domains.ts
server/sonar-web/src/main/js/apps/component-measures/utils.ts
server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap
server/sonar-web/src/main/js/types/metrics.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 5b59046f0dc27ae46fd4e3cf8f131df1b74e6ba8..35eb2d2dfc2d52672b832674a95c7c5ea64868ab 100644 (file)
@@ -17,6 +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 { ComponentQualifier } from '../../../types/component';
 import { MetricKey } from '../../../types/metrics';
 import * as utils from '../utils';
 
@@ -151,3 +152,75 @@ describe('serializeQuery', () => {
     expect(utils.serializeQuery(query)).toBe(utils.serializeQuery(query));
   });
 });
+
+describe('extract measure', () => {
+  const componentBuilder = (qual: ComponentQualifier): T.ComponentMeasure => {
+    return {
+      qualifier: qual,
+      key: '1',
+      name: 'TEST',
+      measures: [
+        {
+          metric: 'alert_status',
+          value: '3.2',
+          periods: [{ index: 1, value: '0.0' }]
+        },
+        {
+          metric: 'releasability_rating',
+          value: '3.2',
+          periods: [{ index: 1, value: '0.0' }]
+        },
+        {
+          metric: 'releasability_effort',
+          value: '3.2',
+          periods: [{ index: 1, value: '0.0' }]
+        }
+      ]
+    };
+  };
+  it('should ban quality gate for app', () => {
+    const measure = utils.banQualityGateMeasure(componentBuilder(ComponentQualifier.Application));
+    expect(measure).toHaveLength(0);
+  });
+
+  it('should ban quality gate for portfolio', () => {
+    const measure = utils.banQualityGateMeasure(componentBuilder(ComponentQualifier.Portfolio));
+    expect(measure).toHaveLength(3);
+  });
+
+  it('should ban quality gate for file', () => {
+    const measure = utils.banQualityGateMeasure(componentBuilder(ComponentQualifier.File));
+    expect(measure).toHaveLength(2);
+    measure.forEach(({ metric }) => {
+      expect(['releasability_rating', 'releasability_effort']).toContain(metric);
+    });
+  });
+});
+
+describe('Component classification', () => {
+  const componentBuilder = (qual: ComponentQualifier): T.ComponentMeasure => {
+    return {
+      qualifier: qual,
+      key: '1',
+      name: 'TEST'
+    };
+  };
+
+  it('should be file type', () => {
+    [ComponentQualifier.File, ComponentQualifier.TestFile].forEach(qual => {
+      const component = componentBuilder(qual);
+      expect(utils.isFileType(component)).toBeTruthy();
+    });
+  });
+
+  it('should be view type', () => {
+    [
+      ComponentQualifier.Portfolio,
+      ComponentQualifier.SubPortfolio,
+      ComponentQualifier.Application
+    ].forEach(qual => {
+      const component = componentBuilder(qual);
+      expect(utils.isViewType(component)).toBeTruthy();
+    });
+  });
+});
index 146d6d1af1975dceb5affbd69f2c1b7ecfcb6f74..dcd2aca7ea47f33a6452689f657d8e7de594e482 100644 (file)
@@ -47,6 +47,7 @@ import { BranchLike } from '../../../types/branch-like';
 import Sidebar from '../sidebar/Sidebar';
 import '../style.css';
 import {
+  banQualityGateMeasure,
   getMeasuresPageMetricKeys,
   groupByDomains,
   hasBubbleChart,
@@ -136,24 +137,13 @@ export class App extends React.PureComponent<Props, State> {
 
     const filteredKeys = getMeasuresPageMetricKeys(metrics, branchLike);
 
-    const banQualityGate = ({ measures = [], qualifier }: T.ComponentMeasure) => {
-      const bannedMetrics: string[] = [];
-      if (!['VW', 'SVW'].includes(qualifier)) {
-        bannedMetrics.push('alert_status', 'security_review_rating');
-      }
-      if (qualifier === 'APP') {
-        bannedMetrics.push('releasability_rating', 'releasability_effort');
-      }
-      return measures.filter(measure => !bannedMetrics.includes(measure.metric));
-    };
-
     getMeasuresAndMeta(componentKey, filteredKeys, {
       additionalFields: 'periods',
       ...getBranchLikeQuery(branchLike)
     }).then(
       ({ component, periods }) => {
         if (this.mounted) {
-          const measures = banQualityGate(component).map(measure =>
+          const measures = banQualityGateMeasure(component).map(measure =>
             enhanceMeasure(measure, metrics)
           );
 
index eb9d15604b22ff0da0f3cb163ea3e0c9a5686a55..844ac0ebdb288509b3fa051de180a4b54c881357 100644 (file)
@@ -46,13 +46,26 @@ export const domains: Domains = {
       MetricKey.new_vulnerabilities,
       MetricKey.new_security_rating,
       MetricKey.new_security_remediation_effort,
-      MetricKey.new_security_hotspots,
 
       'overall_category',
       MetricKey.vulnerabilities,
       MetricKey.security_rating,
-      MetricKey.security_remediation_effort,
-      MetricKey.security_hotspots
+      MetricKey.security_remediation_effort
+    ]
+  },
+
+  SecurityReview: {
+    categories: ['new_code_category', 'overall_category'],
+    order: [
+      'new_code_category',
+      MetricKey.new_security_hotspots,
+      MetricKey.new_security_review_rating,
+      MetricKey.new_security_hotspots_reviewed,
+
+      'overall_category',
+      MetricKey.security_hotspots,
+      MetricKey.security_review_rating,
+      MetricKey.security_hotspots_reviewed
     ]
   },
 
index 6152b43e5d80760942c9eae927e8125f51008c12..f0d9d06113ffea7652fa5def0ff94cc9503e0d8e 100644 (file)
@@ -24,6 +24,7 @@ import { enhanceMeasure } from '../../components/measure/utils';
 import { isBranch, isPullRequest } from '../../helpers/branch-like';
 import { getDisplayMetrics, isDiffMetric } from '../../helpers/measures';
 import { BranchLike } from '../../types/branch-like';
+import { ComponentQualifier } from '../../types/component';
 import { bubbles } from './config/bubbles';
 import { domains } from './config/domains';
 
@@ -36,6 +37,7 @@ export const KNOWN_DOMAINS = [
   'Releasability',
   'Reliability',
   'Security',
+  'SecurityReview',
   'Maintainability',
   'Coverage',
   'Duplications',
@@ -103,11 +105,31 @@ export function enhanceComponent(
 }
 
 export function isFileType(component: T.ComponentMeasure): boolean {
-  return ['FIL', 'UTS'].includes(component.qualifier);
+  return [ComponentQualifier.File, ComponentQualifier.TestFile].includes(
+    component.qualifier as ComponentQualifier
+  );
 }
 
 export function isViewType(component: T.ComponentMeasure): boolean {
-  return ['VW', 'SVW', 'APP'].includes(component.qualifier);
+  return [
+    ComponentQualifier.Portfolio,
+    ComponentQualifier.SubPortfolio,
+    ComponentQualifier.Application
+  ].includes(component.qualifier as ComponentQualifier);
+}
+
+export function banQualityGateMeasure({
+  measures = [],
+  qualifier
+}: T.ComponentMeasure): T.Measure[] {
+  const bannedMetrics: string[] = [];
+  if (ComponentQualifier.Portfolio !== qualifier && ComponentQualifier.SubPortfolio !== qualifier) {
+    bannedMetrics.push('alert_status');
+  }
+  if (qualifier === ComponentQualifier.Application) {
+    bannedMetrics.push('releasability_rating', 'releasability_effort');
+  }
+  return measures.filter(measure => !bannedMetrics.includes(measure.metric));
 }
 
 export const groupByDomains = memoize((measures: T.MeasureEnhanced[]) => {
index 2d92ea3ddc7295b5f124f8a09cdc8819f9176800..cd991cf8d39fe0f2c956ceddce42448478df029d 100644 (file)
@@ -329,7 +329,7 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
       <div className="measures-viewer-card" key={domain}>
         <div className="measures">
           <div className="measure measure-big">
-            <span className="measure-name">{domain}</span>
+            <span className="measure-name">{translate('metric_domain', domain)}</span>
           </div>
           {sortBy(
             measures.filter(measure => measure.value !== undefined),
index 578c4ed68b14d2a070c12659697be78e54306bbf..8943a81bd7ea73b6c01a5b610d28134c5fbe2d7e 100644 (file)
@@ -1146,7 +1146,7 @@ exports[`should render source file 2`] = `
                 <span
                   className="measure-name"
                 >
-                  Complexity
+                  metric_domain.Complexity
                 </span>
               </div>
               <MeasuresOverlayMeasure
@@ -1190,7 +1190,7 @@ exports[`should render source file 2`] = `
                 <span
                   className="measure-name"
                 >
-                  Size
+                  metric_domain.Size
                 </span>
               </div>
               <MeasuresOverlayMeasure
@@ -1312,7 +1312,7 @@ exports[`should render source file 2`] = `
                 <span
                   className="measure-name"
                 >
-                  Coverage
+                  metric_domain.Coverage
                 </span>
               </div>
               <MeasuresOverlayMeasure
@@ -1382,7 +1382,7 @@ exports[`should render source file 2`] = `
                 <span
                   className="measure-name"
                 >
-                  Reliability
+                  metric_domain.Reliability
                 </span>
               </div>
               <MeasuresOverlayMeasure
@@ -1430,7 +1430,7 @@ exports[`should render source file 2`] = `
                 <span
                   className="measure-name"
                 >
-                  Security
+                  metric_domain.Security
                 </span>
               </div>
               <MeasuresOverlayMeasure
@@ -1474,7 +1474,7 @@ exports[`should render source file 2`] = `
                 <span
                   className="measure-name"
                 >
-                  Tests
+                  metric_domain.Tests
                 </span>
               </div>
               <MeasuresOverlayMeasure
@@ -1544,7 +1544,7 @@ exports[`should render source file 2`] = `
                 <span
                   className="measure-name"
                 >
-                  Issues
+                  metric_domain.Issues
                 </span>
               </div>
               <MeasuresOverlayMeasure
@@ -1601,7 +1601,7 @@ exports[`should render source file 2`] = `
                 <span
                   className="measure-name"
                 >
-                  Duplications
+                  metric_domain.Duplications
                 </span>
               </div>
               <MeasuresOverlayMeasure
@@ -1671,7 +1671,7 @@ exports[`should render source file 2`] = `
                 <span
                   className="measure-name"
                 >
-                  Maintainability
+                  metric_domain.Maintainability
                 </span>
               </div>
               <MeasuresOverlayMeasure
index f205a778f11dc73194805783dfc76c50fc7cd81e..e866ec076bc54f1e6568ba87d8aa9f73a1050223 100644 (file)
@@ -96,8 +96,10 @@ export enum MetricKey {
   new_reliability_rating = 'new_reliability_rating',
   new_reliability_remediation_effort = 'new_reliability_remediation_effort',
   new_security_hotspots = 'new_security_hotspots',
+  new_security_hotspots_reviewed = 'new_security_hotspots_reviewed',
   new_security_rating = 'new_security_rating',
   new_security_remediation_effort = 'new_security_remediation_effort',
+  new_security_review_rating = 'new_security_review_rating',
   new_sqale_debt_ratio = 'new_sqale_debt_ratio',
   new_technical_debt = 'new_technical_debt',
   new_uncovered_conditions = 'new_uncovered_conditions',
@@ -118,6 +120,7 @@ export enum MetricKey {
   reliability_remediation_effort = 'reliability_remediation_effort',
   reopened_issues = 'reopened_issues',
   security_hotspots = 'security_hotspots',
+  security_hotspots_reviewed = 'security_hotspots_reviewed',
   security_rating = 'security_rating',
   security_rating_effort = 'security_rating_effort',
   security_remediation_effort = 'security_remediation_effort',
index f8e06bf935290b0057e8d0fe22f2f702ba7b9863..5f2d733dba2cd189058f7eda504ccd5ac9bf9336 100644 (file)
@@ -2113,14 +2113,18 @@ metric.new_security_hotspots.name=New Security Hotspots
 metric.new_security_hotspots.short_name=Security Hotspots
 metric.new_security_hotspots_reviewed.description=Security Hotspots Reviewed on New Code
 metric.new_security_hotspots_reviewed.name=Security Hotspots Reviewed on New Code
+metric.new_security_hotspots_reviewed.short_name=Security Hotspots Reviewed
 metric.new_security_rating.description=Security rating on new code
 metric.new_security_rating.name=Security Rating on New Code
 metric.new_security_rating.extra_short_name=Rating
+metric.new_security_review_rating.description=Security Review rating on new code
+metric.new_security_review_rating.name=Security Review Rating on New Code
 metric.new_security_remediation_effort.description=Security remediation effort on new code
 metric.new_security_remediation_effort.name=Security Remediation Effort on New Code
 metric.new_security_remediation_effort.extra_short_name=Remediation Effort
 metric.new_security_review_rating.description=Security Review Rating on New Code
 metric.new_security_review_rating.name=Security Review Rating on New Code
+metric.new_security_review_rating.extra_short_name=Rating
 metric.new_sqale_debt_ratio.description=Technical Debt Ratio of new/changed code.
 metric.new_sqale_debt_ratio.name=Technical Debt Ratio on New Code
 metric.new_sqale_debt_ratio.short_name=Debt Ratio on new code
@@ -2217,6 +2221,9 @@ metric.rfc_distribution.description=Class distribution /RFC
 metric.rfc_distribution.name=Class Distribution / RFC
 metric.security_hotspots.description=Security Hotspots
 metric.security_hotspots.name=Security Hotspots
+metric.security_hotspots_reviewed.description=Security Hotspots Reviewed
+metric.security_hotspots_reviewed.name=Security Hotspots Reviewed
+metric.security_hotspots_reviewed.extra_short_name=Hotspots Reviewed
 metric.security_rating.description=Security rating
 metric.security_rating.name=Security Rating
 metric.security_rating.extra_short_name=Rating
@@ -2230,10 +2237,7 @@ metric.security_remediation_effort.name=Security Remediation Effort
 metric.security_remediation_effort.extra_short_name=Remediation Effort
 metric.security_review_rating.description=Security Review Rating
 metric.security_review_rating.name=Security Review Rating
-metric.security_review_rating.extra_short_name=Review Rating
-metric.security_hotspots_reviewed.description=Security Hotspots Reviewed
-metric.security_hotspots_reviewed.name=Security Hotspots Reviewed
-metric.security_hotspots_reviewed.extra_short_name=Hotspots Reviewed
+metric.security_review_rating.extra_short_name=Rating
 metric.skipped_tests.description=Number of skipped unit tests
 metric.skipped_tests.name=Skipped Unit Tests
 metric.skipped_tests.short_name=Skipped
@@ -2861,6 +2865,7 @@ component_measures.overview.Duplications.description=See duplications' long-term
 component_measures.domain_facets.Reliability.help=Issues in this domain mark code where you will get behavior other than what was expected.
 component_measures.domain_facets.Maintainability.help=Issues in this domain mark code that will be more difficult to update competently than it should.
 component_measures.domain_facets.Security.help=Issues in this domain mark potential weaknesses to hackers.
+component_measures.domain_facets.SecurityReview.help=This domain represents potential security risks in the form of hotspots and their review status.
 component_measures.domain_facets.Complexity.help=How simple or complicated the control flow of the application is. Cyclomatic Complexity measures the minimum number of test cases required for full test coverage. Cognitive Complexity is a measure of how difficult the application is to understand
 
 component_measures.facet_category.new_code_category=On new code