diff options
author | Stas Vilchik <stas-vilchik@users.noreply.github.com> | 2017-01-25 13:37:43 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-25 13:37:43 +0100 |
commit | 4029e97abc4ee6b5abd2f6e04d9814a8fa31074b (patch) | |
tree | ca99f2a2d1c37bb97854303fdb9e63d0d543bb8d /server/sonar-web/src/main/js/apps/overview | |
parent | d7c99ffe56d2bd41163207395e08d6dd8e7acad2 (diff) | |
download | sonarqube-4029e97abc4ee6b5abd2f6e04d9814a8fa31074b.tar.gz sonarqube-4029e97abc4ee6b5abd2f6e04d9814a8fa31074b.zip |
SONAR-8397 Exploring a failed quality condition on rating is not usable (#1562)
Diffstat (limited to 'server/sonar-web/src/main/js/apps/overview')
5 files changed, 550 insertions, 61 deletions
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 ( - <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; + ); + } +} 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 ( - <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> ); } } 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( + <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 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`] = ` +<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> +`; 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 { |