]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8397 Exploring a failed quality condition on rating is not usable (#1562)
authorStas Vilchik <stas-vilchik@users.noreply.github.com>
Wed, 25 Jan 2017 12:37:43 +0000 (13:37 +0100)
committerGitHub <noreply@github.com>
Wed, 25 Jan 2017 12:37:43 +0000 (13:37 +0100)
server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js
server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.js
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/styles.css

index 06280ad7a528dd8728b271a008582b43b358a8f0..8ec463a3380b242fe042c56cdbac06af5514b18d 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.
  */
+// @flow
 import React from 'react';
 import classNames from 'classnames';
-import { ComponentType, PeriodsListType, EnhancedConditionType } from '../propTypes';
+import { Link } from 'react-router';
 import { DrilldownLink } from '../../../components/shared/drilldown-link';
 import Measure from '../../component-measures/components/Measure';
 import { getPeriodValue, isDiffMetric, formatMeasure } from '../../../helpers/measures';
 import { translate } from '../../../helpers/l10n';
 import { getPeriod, getPeriodDate } from '../../../helpers/periods';
+import { getComponentIssuesUrl } from '../../../helpers/urls';
 
-const QualityGateCondition = ({ component, periods, condition }) => {
-  const { measure } = condition;
-  const { metric } = measure;
+export default class QualityGateCondition extends React.Component {
+  props: {
+    component: { key: string },
+    periods: Array<{
+      index: number,
+      date: string,
+      mode: string,
+      parameter?: string
+    }>,
+    condition: {
+      level: string,
+      measure: {
+        metric: {
+          key: string,
+          name: string,
+          type: string
+        },
+        value: string
+      },
+      op: string,
+      period: number,
+      error: string,
+      warning: string
+    }
+  };
 
-  const isRating = metric.type === 'RATING';
-  const isDiff = isDiffMetric(metric.key);
+  getIssuesUrl (sinceLeakPeriod: boolean, customQuery: {}) {
+    const query: Object = {
+      resolved: 'false',
+      ...customQuery
+    };
+    if (sinceLeakPeriod) {
+      Object.assign(query, { sinceLeakPeriod: 'true' });
+    }
+    return getComponentIssuesUrl(this.props.component.key, query);
+  }
 
-  const threshold = condition.level === 'ERROR' ?
-      condition.error :
-      condition.warning;
+  getUrlForCodeSmells (sinceLeakPeriod: boolean) {
+    return this.getIssuesUrl(sinceLeakPeriod, { types: 'CODE_SMELL' });
+  }
 
-  const actual = condition.period ?
-      getPeriodValue(measure, condition.period) :
-      measure.value;
-  const period = getPeriod(periods, condition.period);
+  getUrlForBugsOrVulnerabilities (type: string, sinceLeakPeriod: boolean) {
+    const RATING_TO_SEVERITIES_MAPPING = {
+      '1': 'BLOCKER,CRITICAL,MAJOR,MINOR',
+      '2': 'BLOCKER,CRITICAL,MAJOR',
+      '3': 'BLOCKER,CRITICAL',
+      '4': 'BLOCKER'
+    };
 
-  const periodDate = getPeriodDate(period);
-  const operator = isRating ?
-      translate('quality_gates.operator', condition.op, 'rating') :
-      translate('quality_gates.operator', condition.op);
+    const { condition } = this.props;
+    const threshold = condition.level === 'ERROR' ? condition.error : condition.warning;
 
-  const className = classNames(
-      'overview-quality-gate-condition',
-      'overview-quality-gate-condition-' + condition.level.toLowerCase(),
-      { 'overview-quality-gate-condition-leak': period != null }
-  );
+    return this.getIssuesUrl(sinceLeakPeriod, {
+      types: type,
+      severities: RATING_TO_SEVERITIES_MAPPING[threshold]
+    });
+  }
 
-  return (
-      <li className={className}>
-        <div className="overview-quality-gate-condition-container">
-          <div className="overview-quality-gate-condition-value">
+  getUrlForType (type: string, sinceLeakPeriod: boolean) {
+    return type === 'CODE_SMELL' ?
+        this.getUrlForCodeSmells(sinceLeakPeriod) :
+        this.getUrlForBugsOrVulnerabilities(type, sinceLeakPeriod);
+  }
+
+  wrapWithLink (children: Object) {
+    const { component, periods, condition } = this.props;
+
+    const period = getPeriod(periods, condition.period);
+    const periodDate = getPeriodDate(period);
+
+    const className = classNames(
+        'overview-quality-gate-condition',
+        'overview-quality-gate-condition-' + condition.level.toLowerCase(),
+        { 'overview-quality-gate-condition-leak': period != null }
+    );
+
+    const metricKey = condition.measure.metric.key;
+
+    const RATING_METRICS_MAPPING = {
+      'reliability_rating': ['BUG', false],
+      'new_reliability_rating': ['BUG', true],
+      'security_rating': ['VULNERABILITY', false],
+      'new_security_rating': ['VULNERABILITY', true],
+      'sqale_rating': ['CODE_SMELL', false],
+      'new_sqale_rating': ['CODE_SMELL', true]
+    };
+
+    return RATING_METRICS_MAPPING[metricKey] ? (
+            <Link to={this.getUrlForType(...RATING_METRICS_MAPPING[metricKey])} className={className}>
+              {children}
+            </Link>
+        ) : (
             <DrilldownLink
-                className={isRating ? 'link-no-underline' : null}
+                className={className}
                 component={component.key}
-                metric={metric.key}
+                metric={condition.measure.metric.key}
                 period={condition.period}
                 periodDate={periodDate}>
-              <Measure measure={{ value: actual, leak: actual }} metric={metric}/>
+              {children}
             </DrilldownLink>
+        );
+  }
+
+  render () {
+    const { periods, condition } = this.props;
+
+    const { measure } = condition;
+    const { metric } = measure;
+
+    const isRating = metric.type === 'RATING';
+    const isDiff = isDiffMetric(metric.key);
+
+    const threshold = condition.level === 'ERROR' ?
+        condition.error :
+        condition.warning;
+
+    const actual = condition.period ?
+        getPeriodValue(measure, condition.period) :
+        measure.value;
+    const period = getPeriod(periods, condition.period);
+
+    const operator = isRating ?
+        translate('quality_gates.operator', condition.op, 'rating') :
+        translate('quality_gates.operator', condition.op);
+
+    return this.wrapWithLink(
+        <div className="overview-quality-gate-condition-container">
+          <div className="overview-quality-gate-condition-value">
+            <Measure measure={{ value: actual, leak: actual }} metric={metric}/>
           </div>
 
           <div>
@@ -81,14 +173,6 @@ const QualityGateCondition = ({ component, periods, condition }) => {
             </div>
           </div>
         </div>
-      </li>
-  );
-};
-
-QualityGateCondition.propTypes = {
-  component: ComponentType.isRequired,
-  periods: PeriodsListType.isRequired,
-  condition: EnhancedConditionType.isRequired
-};
-
-export default QualityGateCondition;
+    );
+  }
+}
index 0c49b1a7dc2741f8d30ff1bfef26712d1ef7ec7f..dffdbd91b046cc71719c07b83f6346c9bf9e7c9a 100644 (file)
@@ -102,9 +102,8 @@ export default class QualityGateConditions extends React.Component {
     );
 
     return (
-        <ul
-            className="overview-quality-gate-conditions-list clearfix"
-            id="overview-quality-gate-conditions-list">
+        <div id="overview-quality-gate-conditions-list"
+             className="overview-quality-gate-conditions-list clearfix">
           {sortedConditions.map(condition => (
               <QualityGateCondition
                   key={condition.measure.metric.key}
@@ -112,7 +111,7 @@ export default class QualityGateConditions extends React.Component {
                   periods={periods}
                   condition={condition}/>
           ))}
-        </ul>
+        </div>
     );
   }
 }
index 4c7ce5c7a3ea0c47f34150e6567d431a65b20b71..245c02b56d1dee551045d67d7b8c39f07a796916 100644 (file)
 import React from 'react';
 import { shallow } from 'enzyme';
 import QualityGateCondition from '../QualityGateCondition';
-import { DrilldownLink } from '../../../../components/shared/drilldown-link';
 
-it('should render DrilldownLink', () => {
-  const component = {
-    id: 'abcd',
-    key: 'abcd-key'
-  };
-  const periods = [];
+const mockRatingCondition = metric => ({
+  actual: '3',
+  error: '1',
+  level: 'ERROR',
+  measure: {
+    metric: {
+      key: metric,
+      type: 'RATING',
+      name: metric
+    },
+    value: '3'
+  },
+  op: 'GT',
+  metric
+});
+
+it('open_issues', () => {
   const condition = {
     actual: '10',
     error: '0',
@@ -36,22 +46,77 @@ it('should render DrilldownLink', () => {
       metric: {
         key: 'open_issues',
         type: 'INT',
-        name: 'Open Issues'
+        name: 'Open open_issues'
       },
       value: '10'
     },
     metric: 'open_issues',
     op: 'GT'
   };
+  expect(shallow(
+      <QualityGateCondition component={{ key: 'abcd-key' }} periods={[]} condition={condition}/>
+  )).toMatchSnapshot();
+});
+
+it('new_open_issues', () => {
+  const condition = {
+    actual: '10',
+    error: '0',
+    level: 'ERROR',
+    measure: {
+      metric: {
+        key: 'new_open_issues',
+        type: 'INT',
+        name: 'new_open_issues'
+      },
+      value: '10'
+    },
+    metric: 'new_open_issues',
+    op: 'GT'
+  };
+  expect(shallow(
+      <QualityGateCondition component={{ key: 'abcd-key' }} periods={[]} condition={condition}/>
+  )).toMatchSnapshot();
+});
+
+it('reliability_rating', () => {
+  const condition = mockRatingCondition('reliability_rating');
+  expect(shallow(
+      <QualityGateCondition component={{ key: 'abcd-key' }} periods={[]} condition={condition}/>
+  )).toMatchSnapshot();
+});
 
-  const output = shallow(
-      <QualityGateCondition
-          component={component}
-          periods={periods}
-          condition={condition}/>
-  );
+it('security_rating', () => {
+  const condition = mockRatingCondition('security_rating');
+  expect(shallow(
+      <QualityGateCondition component={{ key: 'abcd-key' }} periods={[]} condition={condition}/>
+  )).toMatchSnapshot();
+});
+
+it('sqale_rating', () => {
+  const condition = mockRatingCondition('sqale_rating');
+  expect(shallow(
+      <QualityGateCondition component={{ key: 'abcd-key' }} periods={[]} condition={condition}/>
+  )).toMatchSnapshot();
+});
+
+it('new_reliability_rating', () => {
+  const condition = mockRatingCondition('new_reliability_rating');
+  expect(shallow(
+      <QualityGateCondition component={{ key: 'abcd-key' }} periods={[]} condition={condition}/>
+  )).toMatchSnapshot();
+});
+
+it('new_security_rating', () => {
+  const condition = mockRatingCondition('new_security_rating');
+  expect(shallow(
+      <QualityGateCondition component={{ key: 'abcd-key' }} periods={[]} condition={condition}/>
+  )).toMatchSnapshot();
+});
 
-  const link = output.find(DrilldownLink);
-  expect(link.prop('component')).toBe('abcd-key');
-  expect(link.prop('metric')).toBe('open_issues');
+it('new_sqale_rating', () => {
+  const condition = mockRatingCondition('new_sqale_rating');
+  expect(shallow(
+      <QualityGateCondition component={{ key: 'abcd-key' }} periods={[]} condition={condition}/>
+  )).toMatchSnapshot();
 });
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap
new file mode 100644 (file)
index 0000000..25bcdd0
--- /dev/null
@@ -0,0 +1,327 @@
+exports[`test new_open_issues 1`] = `
+<DrilldownLink
+  className="overview-quality-gate-condition overview-quality-gate-condition-error"
+  component="abcd-key"
+  metric="new_open_issues"
+  periodDate={null}>
+  <div
+    className="overview-quality-gate-condition-container">
+    <div
+      className="overview-quality-gate-condition-value">
+      <Measure
+        measure={
+          Object {
+            "leak": "10",
+            "value": "10",
+          }
+        }
+        metric={
+          Object {
+            "key": "new_open_issues",
+            "name": "new_open_issues",
+            "type": "INT",
+          }
+        } />
+    </div>
+    <div>
+      <div
+        className="overview-quality-gate-condition-metric">
+        new_open_issues
+      </div>
+      <div
+        className="overview-quality-gate-threshold">
+        quality_gates.operator.GT
+         
+        0
+      </div>
+    </div>
+  </div>
+</DrilldownLink>
+`;
+
+exports[`test new_reliability_rating 1`] = `
+<Link
+  className="overview-quality-gate-condition overview-quality-gate-condition-error"
+  onlyActiveOnIndex={false}
+  style={Object {}}
+  to="/component_issues?id=abcd-key#resolved=false|types=BUG|severities=BLOCKER%2CCRITICAL%2CMAJOR%2CMINOR|sinceLeakPeriod=true">
+  <div
+    className="overview-quality-gate-condition-container">
+    <div
+      className="overview-quality-gate-condition-value">
+      <Measure
+        measure={
+          Object {
+            "leak": "3",
+            "value": "3",
+          }
+        }
+        metric={
+          Object {
+            "key": "new_reliability_rating",
+            "name": "new_reliability_rating",
+            "type": "RATING",
+          }
+        } />
+    </div>
+    <div>
+      <div
+        className="overview-quality-gate-condition-metric">
+        new_reliability_rating
+      </div>
+      <div
+        className="overview-quality-gate-threshold">
+        quality_gates.operator.GT.rating
+         
+        A
+      </div>
+    </div>
+  </div>
+</Link>
+`;
+
+exports[`test new_security_rating 1`] = `
+<Link
+  className="overview-quality-gate-condition overview-quality-gate-condition-error"
+  onlyActiveOnIndex={false}
+  style={Object {}}
+  to="/component_issues?id=abcd-key#resolved=false|types=VULNERABILITY|severities=BLOCKER%2CCRITICAL%2CMAJOR%2CMINOR|sinceLeakPeriod=true">
+  <div
+    className="overview-quality-gate-condition-container">
+    <div
+      className="overview-quality-gate-condition-value">
+      <Measure
+        measure={
+          Object {
+            "leak": "3",
+            "value": "3",
+          }
+        }
+        metric={
+          Object {
+            "key": "new_security_rating",
+            "name": "new_security_rating",
+            "type": "RATING",
+          }
+        } />
+    </div>
+    <div>
+      <div
+        className="overview-quality-gate-condition-metric">
+        new_security_rating
+      </div>
+      <div
+        className="overview-quality-gate-threshold">
+        quality_gates.operator.GT.rating
+         
+        A
+      </div>
+    </div>
+  </div>
+</Link>
+`;
+
+exports[`test new_sqale_rating 1`] = `
+<Link
+  className="overview-quality-gate-condition overview-quality-gate-condition-error"
+  onlyActiveOnIndex={false}
+  style={Object {}}
+  to="/component_issues?id=abcd-key#resolved=false|types=CODE_SMELL|sinceLeakPeriod=true">
+  <div
+    className="overview-quality-gate-condition-container">
+    <div
+      className="overview-quality-gate-condition-value">
+      <Measure
+        measure={
+          Object {
+            "leak": "3",
+            "value": "3",
+          }
+        }
+        metric={
+          Object {
+            "key": "new_sqale_rating",
+            "name": "new_sqale_rating",
+            "type": "RATING",
+          }
+        } />
+    </div>
+    <div>
+      <div
+        className="overview-quality-gate-condition-metric">
+        new_sqale_rating
+      </div>
+      <div
+        className="overview-quality-gate-threshold">
+        quality_gates.operator.GT.rating
+         
+        A
+      </div>
+    </div>
+  </div>
+</Link>
+`;
+
+exports[`test open_issues 1`] = `
+<DrilldownLink
+  className="overview-quality-gate-condition overview-quality-gate-condition-error"
+  component="abcd-key"
+  metric="open_issues"
+  periodDate={null}>
+  <div
+    className="overview-quality-gate-condition-container">
+    <div
+      className="overview-quality-gate-condition-value">
+      <Measure
+        measure={
+          Object {
+            "leak": "10",
+            "value": "10",
+          }
+        }
+        metric={
+          Object {
+            "key": "open_issues",
+            "name": "Open open_issues",
+            "type": "INT",
+          }
+        } />
+    </div>
+    <div>
+      <div
+        className="overview-quality-gate-condition-metric">
+        Open open_issues
+      </div>
+      <div
+        className="overview-quality-gate-threshold">
+        quality_gates.operator.GT
+         
+        0
+      </div>
+    </div>
+  </div>
+</DrilldownLink>
+`;
+
+exports[`test reliability_rating 1`] = `
+<Link
+  className="overview-quality-gate-condition overview-quality-gate-condition-error"
+  onlyActiveOnIndex={false}
+  style={Object {}}
+  to="/component_issues?id=abcd-key#resolved=false|types=BUG|severities=BLOCKER%2CCRITICAL%2CMAJOR%2CMINOR">
+  <div
+    className="overview-quality-gate-condition-container">
+    <div
+      className="overview-quality-gate-condition-value">
+      <Measure
+        measure={
+          Object {
+            "leak": "3",
+            "value": "3",
+          }
+        }
+        metric={
+          Object {
+            "key": "reliability_rating",
+            "name": "reliability_rating",
+            "type": "RATING",
+          }
+        } />
+    </div>
+    <div>
+      <div
+        className="overview-quality-gate-condition-metric">
+        reliability_rating
+      </div>
+      <div
+        className="overview-quality-gate-threshold">
+        quality_gates.operator.GT.rating
+         
+        A
+      </div>
+    </div>
+  </div>
+</Link>
+`;
+
+exports[`test security_rating 1`] = `
+<Link
+  className="overview-quality-gate-condition overview-quality-gate-condition-error"
+  onlyActiveOnIndex={false}
+  style={Object {}}
+  to="/component_issues?id=abcd-key#resolved=false|types=VULNERABILITY|severities=BLOCKER%2CCRITICAL%2CMAJOR%2CMINOR">
+  <div
+    className="overview-quality-gate-condition-container">
+    <div
+      className="overview-quality-gate-condition-value">
+      <Measure
+        measure={
+          Object {
+            "leak": "3",
+            "value": "3",
+          }
+        }
+        metric={
+          Object {
+            "key": "security_rating",
+            "name": "security_rating",
+            "type": "RATING",
+          }
+        } />
+    </div>
+    <div>
+      <div
+        className="overview-quality-gate-condition-metric">
+        security_rating
+      </div>
+      <div
+        className="overview-quality-gate-threshold">
+        quality_gates.operator.GT.rating
+         
+        A
+      </div>
+    </div>
+  </div>
+</Link>
+`;
+
+exports[`test sqale_rating 1`] = `
+<Link
+  className="overview-quality-gate-condition overview-quality-gate-condition-error"
+  onlyActiveOnIndex={false}
+  style={Object {}}
+  to="/component_issues?id=abcd-key#resolved=false|types=CODE_SMELL">
+  <div
+    className="overview-quality-gate-condition-container">
+    <div
+      className="overview-quality-gate-condition-value">
+      <Measure
+        measure={
+          Object {
+            "leak": "3",
+            "value": "3",
+          }
+        }
+        metric={
+          Object {
+            "key": "sqale_rating",
+            "name": "sqale_rating",
+            "type": "RATING",
+          }
+        } />
+    </div>
+    <div>
+      <div
+        className="overview-quality-gate-condition-metric">
+        sqale_rating
+      </div>
+      <div
+        className="overview-quality-gate-threshold">
+        quality_gates.operator.GT.rating
+         
+        A
+      </div>
+    </div>
+  </div>
+</Link>
+`;
index 609305faa346bece30788e3e6230c2d2ba12d60d..7bf16ff19415608788ff913dc474e6e27cf12865 100644 (file)
 
 .overview-quality-gate-condition {
   float: left;
+  display: block;
   margin-top: 15px;
   margin-right: 30px;
   border: 1px solid #e6e6e6;
   border-radius: 2px;
   background-color: #fff;
+  color: #444;
+  transition: none;
+}
+
+.overview-quality-gate-condition:hover,
+.overview-quality-gate-condition:focus {
+  border-width: 2px;
+  color: #444;
+}
+
+.overview-quality-gate-condition:hover .overview-quality-gate-condition-container,
+.overview-quality-gate-condition:focus .overview-quality-gate-condition-container {
+  padding: 9px;
 }
 
 .overview-quality-gate-condition-leak {