From: Stas Vilchik Date: Wed, 25 Jan 2017 12:37:43 +0000 (+0100) Subject: SONAR-8397 Exploring a failed quality condition on rating is not usable (#1562) X-Git-Tag: 6.3-RC1~396 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=4029e97abc4ee6b5abd2f6e04d9814a8fa31074b;p=sonarqube.git SONAR-8397 Exploring a failed quality condition on rating is not usable (#1562) --- diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js index 06280ad7a52..8ec463a3380 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js @@ -17,54 +17,146 @@ * 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 ( -
  • -
    -
    + 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] ? ( + + {children} + + ) : ( - + {children} + ); + } + + 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( +
    +
    +
    @@ -81,14 +173,6 @@ const QualityGateCondition = ({ component, periods, condition }) => {
    -
  • - ); -}; - -QualityGateCondition.propTypes = { - component: ComponentType.isRequired, - periods: PeriodsListType.isRequired, - condition: EnhancedConditionType.isRequired -}; - -export default QualityGateCondition; + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js index 0c49b1a7dc2..dffdbd91b04 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateConditions.js @@ -102,9 +102,8 @@ export default class QualityGateConditions extends React.Component { ); return ( - + ); } } diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.js index 4c7ce5c7a3e..245c02b56d1 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.js @@ -20,14 +20,24 @@ 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( + + )).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( + + )).toMatchSnapshot(); +}); + +it('reliability_rating', () => { + const condition = mockRatingCondition('reliability_rating'); + expect(shallow( + + )).toMatchSnapshot(); +}); - const output = shallow( - - ); +it('security_rating', () => { + const condition = mockRatingCondition('security_rating'); + expect(shallow( + + )).toMatchSnapshot(); +}); + +it('sqale_rating', () => { + const condition = mockRatingCondition('sqale_rating'); + expect(shallow( + + )).toMatchSnapshot(); +}); + +it('new_reliability_rating', () => { + const condition = mockRatingCondition('new_reliability_rating'); + expect(shallow( + + )).toMatchSnapshot(); +}); + +it('new_security_rating', () => { + const condition = mockRatingCondition('new_security_rating'); + expect(shallow( + + )).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( + + )).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 index 00000000000..25bcdd01357 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap @@ -0,0 +1,327 @@ +exports[`test new_open_issues 1`] = ` + +
    +
    + +
    +
    +
    + new_open_issues +
    +
    + quality_gates.operator.GT + + 0 +
    +
    +
    +
    +`; + +exports[`test new_reliability_rating 1`] = ` + +
    +
    + +
    +
    +
    + new_reliability_rating +
    +
    + quality_gates.operator.GT.rating + + A +
    +
    +
    + +`; + +exports[`test new_security_rating 1`] = ` + +
    +
    + +
    +
    +
    + new_security_rating +
    +
    + quality_gates.operator.GT.rating + + A +
    +
    +
    + +`; + +exports[`test new_sqale_rating 1`] = ` + +
    +
    + +
    +
    +
    + new_sqale_rating +
    +
    + quality_gates.operator.GT.rating + + A +
    +
    +
    + +`; + +exports[`test open_issues 1`] = ` + +
    +
    + +
    +
    +
    + Open open_issues +
    +
    + quality_gates.operator.GT + + 0 +
    +
    +
    +
    +`; + +exports[`test reliability_rating 1`] = ` + +
    +
    + +
    +
    +
    + reliability_rating +
    +
    + quality_gates.operator.GT.rating + + A +
    +
    +
    + +`; + +exports[`test security_rating 1`] = ` + +
    +
    + +
    +
    +
    + security_rating +
    +
    + quality_gates.operator.GT.rating + + A +
    +
    +
    + +`; + +exports[`test sqale_rating 1`] = ` + +
    +
    + +
    +
    +
    + sqale_rating +
    +
    + quality_gates.operator.GT.rating + + A +
    +
    +
    + +`; diff --git a/server/sonar-web/src/main/js/apps/overview/styles.css b/server/sonar-web/src/main/js/apps/overview/styles.css index 609305faa34..7bf16ff1941 100644 --- a/server/sonar-web/src/main/js/apps/overview/styles.css +++ b/server/sonar-web/src/main/js/apps/overview/styles.css @@ -47,11 +47,25 @@ .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 {