* 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>
</div>
</div>
</div>
- </li>
- );
-};
-
-QualityGateCondition.propTypes = {
- component: ComponentType.isRequired,
- periods: PeriodsListType.isRequired,
- condition: EnhancedConditionType.isRequired
-};
-
-export default QualityGateCondition;
+ );
+ }
+}
);
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}
periods={periods}
condition={condition}/>
))}
- </ul>
+ </div>
);
}
}
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',
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();
});
--- /dev/null
+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>
+`;
.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 {