return <div className="overview-detailed-measure">
<div className="overview-detailed-measure-nutshell">
- <span>{localizeMetric(this.props.metric)}</span>
- <DrilldownLink component={this.props.component.key} metric={this.props.metric}>
- <span className="overview-detailed-measure-value">{formatMeasure(measure, this.props.type)}</span>
- </DrilldownLink>
+ <span className="overview-detailed-measure-name">{localizeMetric(this.props.metric)}</span>
+ <span className="overview-detailed-measure-value">
+ <DrilldownLink component={this.props.component.key} metric={this.props.metric}>
+ {formatMeasure(measure, this.props.type)}
+ </DrilldownLink>
+ </span>
{this.props.children}
</div>
{this.renderLeak()}
+++ /dev/null
-import React from 'react';
-import { DomainBubbleChart } from '../domain/bubble-chart';
-
-
-export class CoverageBubbleChart extends React.Component {
- render () {
- return <DomainBubbleChart {...this.props}
- xMetric="complexity"
- yMetric="coverage"
- sizeMetrics={['sqale_index']}/>;
- }
-}
+++ /dev/null
-import React from 'react';
-
-import { getMeasures } from '../../../api/measures';
-import DrilldownLink from '../helpers/drilldown-link';
-import { formatMeasure } from '../../../helpers/measures';
-
-
-const METRICS = [
- 'coverage',
- 'line_coverage',
- 'branch_coverage',
- 'it_coverage',
- 'it_line_coverage',
- 'it_branch_coverage',
- 'overall_coverage',
- 'overall_line_coverage',
- 'overall_branch_coverage'
-];
-
-
-export class CoverageDetails extends React.Component {
- constructor () {
- super();
- this.state = { measures: {} };
- }
-
- componentDidMount () {
- this.requestDetails();
- }
-
- requestDetails () {
- return getMeasures(this.props.component.key, METRICS).then(measures => {
- this.setState({ measures });
- });
- }
-
- renderValue (value, metricKey) {
- if (value != null) {
- return <DrilldownLink component={this.props.component.key} metric={metricKey}>
- {formatMeasure(value, 'PERCENT')}
- </DrilldownLink>;
- } else {
- return '—';
- }
- }
-
- renderCoverage (coverage, lineCoverage, branchCoverage, prefix) {
- return <table className="data zebra">
- <tbody>
- <tr>
- <td>Coverage</td>
- <td className="thin nowrap text-right">
- {this.renderValue(coverage, prefix + 'coverage')}
- </td>
- </tr>
- <tr>
- <td>Line Coverage</td>
- <td className="thin nowrap text-right">
- {this.renderValue(lineCoverage, prefix + 'line_coverage')}
- </td>
- </tr>
- <tr>
- <td>Branch Coverage</td>
- <td className="thin nowrap text-right">
- {this.renderValue(branchCoverage, prefix + 'branch_coverage')}
- </td>
- </tr>
- </tbody>
- </table>;
- }
-
- renderUTCoverage () {
- if (this.state.measures['coverage'] == null) {
- return null;
- }
- return <div className="big-spacer-top">
- <h4 className="spacer-bottom">Unit Tests</h4>
- {this.renderCoverage(
- this.state.measures['coverage'],
- this.state.measures['line_coverage'],
- this.state.measures['branch_coverage'],
- '')}
- </div>;
- }
-
- renderITCoverage () {
- if (this.state.measures['it_coverage'] == null) {
- return null;
- }
- return <div className="big-spacer-top">
- <h4 className="spacer-bottom">Integration Tests</h4>
- {this.renderCoverage(
- this.state.measures['it_coverage'],
- this.state.measures['it_line_coverage'],
- this.state.measures['it_branch_coverage'],
- 'it_')}
- </div>;
- }
-
- renderOverallCoverage () {
- if (this.state.measures['coverage'] == null ||
- this.state.measures['it_coverage'] == null ||
- this.state.measures['overall_coverage'] == null) {
- return null;
- }
- return <div className="big-spacer-top">
- <h4 className="spacer-bottom">Overall</h4>
- {this.renderCoverage(
- this.state.measures['overall_coverage'],
- this.state.measures['overall_line_coverage'],
- this.state.measures['overall_branch_coverage'],
- 'overall_')}
- </div>;
- }
-
- render () {
- return <div className="overview-domain-section">
- <h2 className="overview-title">Coverage Details</h2>
- {this.renderUTCoverage()}
- {this.renderITCoverage()}
- {this.renderOverallCoverage()}
- </div>;
- }
-}
--- /dev/null
+import React from 'react';
+
+import { formatMeasure, formatMeasureVariation, localizeMetric } from '../../../helpers/measures';
+import DrilldownLink from '../helpers/drilldown-link';
+import { getShortType } from '../helpers/metrics';
+import { DonutChart } from '../../../components/charts/donut-chart';
+
+
+export const CoverageMeasure = React.createClass({
+ renderLeakVariation () {
+ if (!this.props.leakPeriodDate) {
+ return null;
+ }
+ let leak = this.props.leak[this.props.metric];
+ return <div className="overview-detailed-measure-leak">
+ <span className="overview-detailed-measure-value">
+ {formatMeasureVariation(leak, getShortType(this.props.type))}
+ </span>
+ </div>;
+ },
+
+ renderLeakValue () {
+ if (!this.props.leakPeriodDate) {
+ return null;
+ }
+
+ if (!this.props.leakMetric) {
+ return <div className="overview-detailed-measure-leak"> </div>;
+ }
+
+ let leak = this.props.leak[this.props.leakMetric];
+
+ let donutData = [
+ { value: leak, fill: '#85bb43' },
+ { value: 100 - leak, fill: '#d4333f' }
+ ];
+
+ return <div className="overview-detailed-measure-leak">
+ <div className="overview-donut-chart">
+ <DonutChart width="20" height="20" thickness="3" data={donutData}/>
+ </div>
+ <span className="overview-detailed-measure-value">
+ <DrilldownLink component={this.props.component.key} metric={this.props.leakMetric}
+ period={this.props.leakPeriodIndex}>
+ {formatMeasure(leak, this.props.type)}
+ </DrilldownLink>
+ </span>
+ </div>;
+ },
+
+ renderDonut (measure) {
+ if (this.props.metric !== 'PERCENT') {
+ return null;
+ }
+
+ let donutData = [
+ { value: measure, fill: '#85bb43' },
+ { value: 100 - measure, fill: '#d4333f' }
+ ];
+ return <div className="overview-donut-chart">
+ <DonutChart width="20" height="20" thickness="3" data={donutData}/>
+ </div>;
+ },
+
+ render () {
+ let measure = this.props.measures[this.props.metric];
+ if (measure == null) {
+ return null;
+ }
+
+ return <div className="overview-detailed-measure">
+ <div className="overview-detailed-measure-nutshell">
+ <span className="overview-detailed-measure-name">{localizeMetric(this.props.metric)}</span>
+ {this.renderDonut(measure)}
+ <span className="overview-detailed-measure-value">
+ <DrilldownLink component={this.props.component.key} metric={this.props.metric}>
+ {formatMeasure(measure, this.props.type)}
+ </DrilldownLink>
+ </span>
+ </div>
+ {this.renderLeakValue()}
+ {this.renderLeakVariation()}
+ </div>;
+ }
+});
+import _ from 'underscore';
+import d3 from 'd3';
import React from 'react';
-import { CoverageDetails } from './coverage-details';
-import { TestsDetails } from './tests-details';
-import { CoverageBubbleChart } from './bubble-chart';
-import { CoverageTimeline } from './timeline';
-import { CoverageTreemap } from './treemap';
+import { getMeasuresAndVariations } from '../../../api/measures';
+import { DetailedMeasure } from '../common-components';
+import { DomainTimeline } from '../timeline/domain-timeline';
+import { DomainTreemap } from '../domain/treemap';
+import { DomainBubbleChart } from '../domain/bubble-chart';
+import { getPeriodLabel, getPeriodDate } from './../helpers/period-label';
+import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
+import { filterMetrics, filterMetricsForDomains } from '../helpers/metrics';
+import { Legend } from '../common-components';
+import { CHART_COLORS_RANGE_PERCENT } from '../../../helpers/constants';
+import { formatMeasure, formatMeasureVariation, localizeMetric } from '../../../helpers/measures';
+import { DonutChart } from '../../../components/charts/donut-chart';
+import DrilldownLink from '../helpers/drilldown-link';
+import { CoverageMeasure } from './coverage-measure';
-export default class extends React.Component {
- render () {
- return <div className="overview-detailed-page">
- <div className="overview-domain-header">
- <h2 className="overview-title">Coverage & Tests</h2>
- </div>
+const UT_COVERAGE_METRICS = ['coverage', 'new_coverage', 'branch_coverage', 'line_coverage', 'uncovered_conditions',
+ 'uncovered_lines'];
+const IT_COVERAGE_METRICS = ['it_coverage', 'new_it_coverage', 'it_branch_coverage', 'it_line_coverage',
+ 'it_uncovered_conditions', 'it_uncovered_lines'];
+const OVERALL_COVERAGE_METRICS = ['overall_coverage', 'new_overall_coverage', 'overall_branch_coverage',
+ 'overall_line_coverage', 'overall_uncovered_conditions', 'overall_uncovered_lines'];
+const TEST_METRICS = ['tests', 'test_execution_time', 'test_errors', 'test_failures', 'skipped_tests',
+ 'test_success_density'];
+const KNOWN_METRICS = [].concat(TEST_METRICS, OVERALL_COVERAGE_METRICS, UT_COVERAGE_METRICS, IT_COVERAGE_METRICS);
- <a className="overview-detailed-page-back" href="#">
- <i className="icon-chevron-left"/>
- </a>
- <CoverageTimeline {...this.props}/>
+export const CoverageMain = React.createClass({
+ mixins: [TooltipsMixin],
- <div className="flex-columns">
- <div className="flex-column flex-column-half">
- <CoverageDetails {...this.props}/>
- </div>
- <div className="flex-column flex-column-half">
- <TestsDetails {...this.props}/>
+ getInitialState() {
+ return {
+ ready: false,
+ leakPeriodLabel: getPeriodLabel(this.props.component.periods, this.props.leakPeriodIndex),
+ leakPeriodDate: getPeriodDate(this.props.component.periods, this.props.leakPeriodIndex)
+ };
+ },
+
+ componentDidMount() {
+ this.requestMeasures().then(r => {
+ let measures = this.getMeasuresValues(r, 'value');
+ let leak = this.getMeasuresValues(r, 'var' + this.props.leakPeriodIndex);
+ this.setState({ ready: true, measures, leak });
+ });
+ },
+
+ getMeasuresValues (measures, fieldKey) {
+ let values = {};
+ Object.keys(measures).forEach(measureKey => {
+ values[measureKey] = measures[measureKey][fieldKey];
+ });
+ return values;
+ },
+
+ getMetricsForDomain() {
+ return this.props.metrics
+ .filter(metric => ['Tests', 'Tests (Integration)', 'Tests (Overall)'].indexOf(metric.domain) !== -1)
+ .map(metric => metric.key);
+ },
+
+ getMetricsForTimeline() {
+ return filterMetricsForDomains(this.props.metrics, ['Tests', 'Tests (Integration)', 'Tests (Overall)']);
+ },
+
+ getAllMetricsForTimeline() {
+ return filterMetrics(this.props.metrics);
+ },
+
+ requestMeasures () {
+ return getMeasuresAndVariations(this.props.component.key, this.getMetricsForDomain());
+ },
+
+ renderLoading () {
+ return <div className="text-center">
+ <i className="spinner spinner-margin"/>
+ </div>;
+ },
+
+ renderLegend () {
+ return <Legend leakPeriodDate={this.state.leakPeriodDate} leakPeriodLabel={this.state.leakPeriodLabel}/>;
+ },
+
+ renderOtherMeasures() {
+ let metrics = filterMetricsForDomains(this.props.metrics, ['Tests', 'Tests (Integration)', 'Tests (Overall)'])
+ .filter(metric => KNOWN_METRICS.indexOf(metric.key) === -1)
+ .map(metric => metric.key);
+ return this.renderListOfMeasures(metrics);
+ },
+
+ renderUTCoverage () {
+ let hasBothTypes = this.state.measures['coverage'] != null && this.state.measures['it_coverage'] != null;
+ if (!hasBothTypes) {
+ return null;
+ }
+ return <div className="overview-detailed-measures-list">
+ <CoverageMeasure {...this.props} {...this.state} metric="coverage" leakMetric="new_coverage" type="PERCENT"/>
+ <CoverageMeasure {...this.props} {...this.state} metric="line_coverage" leakMetric="new_line_coverage" type="PERCENT"/>
+ <CoverageMeasure {...this.props} {...this.state} metric="branch_coverage" leakMetric="new_branch_coverage" type="PERCENT"/>
+
+ <CoverageMeasure {...this.props} {...this.state} metric="uncovered_lines" type="INT"/>
+ <CoverageMeasure {...this.props} {...this.state} metric="uncovered_conditions" type="INT"/>
+ </div>;
+ },
+
+ renderITCoverage () {
+ let hasBothTypes = this.state.measures['coverage'] != null && this.state.measures['it_coverage'] != null;
+ if (!hasBothTypes) {
+ return null;
+ }
+ return <div className="overview-detailed-measures-list">
+ <CoverageMeasure {...this.props} {...this.state} metric="it_coverage" leakMetric="new_it_coverage" type="PERCENT"/>
+ <CoverageMeasure {...this.props} {...this.state} metric="it_line_coverage" leakMetric="new_it_line_coverage" type="PERCENT"/>
+ <CoverageMeasure {...this.props} {...this.state} metric="it_branch_coverage" leakMetric="new_it_branch_coverage" type="PERCENT"/>
+
+ <CoverageMeasure {...this.props} {...this.state} metric="it_uncovered_lines" type="INT"/>
+ <CoverageMeasure {...this.props} {...this.state} metric="it_uncovered_conditions" type="INT"/>
+ </div>;
+ },
+
+ renderOverallCoverage () {
+ return <div className="overview-detailed-measures-list">
+ <CoverageMeasure {...this.props} {...this.state} metric="overall_coverage" leakMetric="new_overall_coverage" type="PERCENT"/>
+ <CoverageMeasure {...this.props} {...this.state} metric="overall_line_coverage" leakMetric="new_overall_line_coverage" type="PERCENT"/>
+ <CoverageMeasure {...this.props} {...this.state} metric="overall_branch_coverage" leakMetric="new_overall_branch_coverage" type="PERCENT"/>
+
+ <CoverageMeasure {...this.props} {...this.state} metric="overall_uncovered_lines" type="INT"/>
+ <CoverageMeasure {...this.props} {...this.state} metric="overall_uncovered_conditions" type="INT"/>
+ </div>;
+ },
+
+ renderListOfMeasures(list) {
+ let metrics = list
+ .map(key => _.findWhere(this.props.metrics, { key }))
+ .map(metric => {
+ return <DetailedMeasure key={metric.key} {...this.props} {...this.state} metric={metric.key}
+ type={metric.type}/>;
+ });
+ return <div className="overview-detailed-measures-list">{metrics}</div>;
+ },
+
+ render () {
+ if (!this.state.ready) {
+ return this.renderLoading();
+ }
+ let treemapScale = d3.scale.linear()
+ .domain([0, 100])
+ .range(CHART_COLORS_RANGE_PERCENT);
+ return <div className="overview-detailed-page">
+ <div className="overview-domain-charts">
+ <div className="overview-domain">
+ <div className="overview-domain-header">
+ <div className="overview-title">Tests Overview</div>
+ {this.renderLegend()}
+ </div>
+ {this.renderOverallCoverage()}
+ {this.renderUTCoverage()}
+ {this.renderITCoverage()}
+ {this.renderListOfMeasures(TEST_METRICS)}
+ {this.renderOtherMeasures()}
</div>
+ <DomainBubbleChart {...this.props}
+ xMetric="complexity"
+ yMetric="overall_coverage"
+ sizeMetrics={['overall_uncovered_lines']}/>
</div>
- <CoverageBubbleChart {...this.props}/>
- <CoverageTreemap {...this.props}/>
+ <div className="overview-domain-charts">
+ <DomainTimeline {...this.props} {...this.state}
+ initialMetric="overall_coverage"
+ metrics={this.getMetricsForTimeline()}
+ allMetrics={this.getAllMetricsForTimeline()}/>
+ <DomainTreemap {...this.props}
+ sizeMetric="ncloc"
+ colorMetric="overall_coverage"
+ scale={treemapScale}/>
+ </div>
</div>;
+
}
-}
+});
+++ /dev/null
-import React from 'react';
-
-import { DomainMeasuresList } from '../domain/measures-list';
-
-
-const METRICS = [
- 'tests',
- 'skipped_tests',
- 'test_errors',
- 'test_failures',
- 'test_execution_time',
- 'test_success_density'
-];
-
-
-export class TestsDetails extends React.Component {
- render () {
- return <div className="overview-domain-section">
- <h2 className="overview-title">Tests</h2>
- <DomainMeasuresList {...this.props} metricsToDisplay={METRICS}/>
- </div>;
- }
-}
+++ /dev/null
-import React from 'react';
-
-import { DomainTimeline } from '../domain/timeline';
-import { filterMetricsForDomains } from '../helpers/metrics';
-
-
-const DOMAINS = [
- 'Tests',
- 'Tests (Integration)',
- 'Tests (Overall)'
-];
-
-
-export class CoverageTimeline extends React.Component {
- render () {
- return <DomainTimeline {...this.props}
- initialMetric="coverage"
- metrics={filterMetricsForDomains(this.props.metrics, DOMAINS)}/>;
- }
-}
+++ /dev/null
-import d3 from 'd3';
-import React from 'react';
-
-import { DomainTreemap } from '../domain/treemap';
-
-
-const COLORS_5 = ['#ee0000', '#f77700', '#ffee00', '#80cc00', '#00aa00'];
-
-
-export class CoverageTreemap extends React.Component {
- render () {
- let scale = d3.scale.linear()
- .domain([0, 25, 50, 75, 100])
- .range(COLORS_5);
- return <DomainTreemap {...this.props}
- sizeMetric="ncloc"
- colorMetric="coverage"
- scale={scale}/>;
- }
-}
}
return <Domain>
- <DomainHeader title="Tests"/>
+ <DomainHeader title="Tests" linkTo="/tests"/>
<DomainPanel domain="coverage">
<DomainNutshell>
import Meta from './meta';
import { SizeMain } from './size/main';
import { DuplicationsMain } from './duplications/main';
+import { CoverageMain } from './coverage/main';
import { getMetrics } from '../../api/metrics';
import { RouterMixin } from '../../components/router/router';
</div>;
},
+ renderTests () {
+ return <div className="overview">
+ <CoverageMain {...this.props} {...this.state}/>
+ </div>;
+ },
+
render () {
if (!this.state.ready) {
return this.renderLoading();
return this.renderSize();
case '/duplications':
return this.renderDuplications();
+ case '/tests':
+ return this.renderTests();
default:
throw new Error('Unknown route: ' + this.state.route);
}
.domain([0, d3.max(this.props.items, d => d.size)])
.range(this.props.sizeRange);
+ let xScaleOriginal = xScale.copy();
+ let yScaleOriginal = yScale.copy();
+
xScale.range(this.getXRange(xScale, sizeScale, availableWidth));
yScale.range(this.getYRange(yScale, sizeScale, availableHeight));
return <svg className="bubble-chart" width={this.state.width} height={this.state.height}>
<g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}>
{this.renderXGrid(xScale, yScale)}
- {this.renderXTicks(xScale, yScale)}
+ {this.renderXTicks(xScale, yScaleOriginal)}
{this.renderYGrid(xScale, yScale)}
- {this.renderYTicks(xScale, yScale)}
+ {this.renderYTicks(xScaleOriginal, yScale)}
{bubbles}
</g>
</svg>;
--- /dev/null
+import d3 from 'd3';
+import React from 'react';
+
+import { ResizeMixin } from './../mixins/resize-mixin';
+import { TooltipsMixin } from './../mixins/tooltips-mixin';
+
+
+const Sector = React.createClass({
+ render() {
+ let arc = d3.svg.arc()
+ .outerRadius(this.props.radius)
+ .innerRadius(this.props.radius - this.props.thickness);
+ return <path d={arc(this.props.data)} style={{ fill: this.props.fill }}/>;
+ }
+});
+
+
+export const DonutChart = React.createClass({
+ mixins: [ResizeMixin, TooltipsMixin],
+
+ propTypes: {
+ data: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
+ },
+
+ getDefaultProps() {
+ return { thickness: 6, padding: [0, 0, 0, 0] };
+ },
+
+ getInitialState() {
+ return { width: this.props.width, height: this.props.height };
+ },
+
+ render () {
+ if (!this.state.width || !this.state.height) {
+ return <div/>;
+ }
+
+ let availableWidth = this.state.width - this.props.padding[1] - this.props.padding[3];
+ let availableHeight = this.state.height - this.props.padding[0] - this.props.padding[2];
+
+ let size = Math.min(availableWidth, availableHeight),
+ radius = Math.floor(size / 2);
+
+ let pie = d3.layout.pie()
+ .sort(null)
+ .value(d => d.value);
+ let sectors = pie(this.props.data).map((d, i) => {
+ return <Sector key={i} data={d} radius={radius} fill={this.props.data[i].fill} thickness={this.props.thickness}/>;
+ });
+
+ return <svg className="donut-chart" width={this.state.width} height={this.state.height}>
+ <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0]})`}>
+ <g transform={`translate(${radius}, ${radius})`}>
+ {sectors}
+ </g>
+ </g>
+ </svg>;
+ }
+});
border-radius: 2px;
}
+.flex-1 {
+ flex: 1;
+}
+
// Background Color
overflow: hidden;
}
+.overview-detailed-measures-list + .overview-detailed-measures-list {
+ margin-top: 40px;
+}
+
.overview-detailed-measure {
display: flex;
background-color: #fff;
}
.overview-detailed-measure-nutshell,
-.overview-detailed-measure-leak {
+.overview-detailed-measure-leak,
+.overview-detailed-measure-chart {
position: relative;
padding: 7px 10px;
+
+ & & {
+ padding-right: 0;
+ }
}
.overview-detailed-measure-nutshell {
text-align: center;
}
+.overview-detailed-measure-name {
+ flex: 1;
+}
+
.overview-detailed-measure-value {
font-size: 16px;
}
}
}
+.overview-donut-chart {
+ display: inline-block;
+ vertical-align: top;
+ margin-right: 8px;
+}
+
+
+
/*
* Responsive Stuff
*/